BeepSwitch
Animated toggle switch with painter-based rendering, drag-to-toggle, icon library integration, and data binding
Table of Contents
Overview
Namespace: TheTechIdea.Beep.Winform.Controls
Assembly: TheTechIdea.Beep.Winform.Controls
Inheritance: BeepSwitch → BaseControl → System.Windows.Forms.UserControl
Implements: IBeepComponent (via IBeepUIComponent methods)
BeepSwitch is a capsule-shaped toggle switch control with a pluggable painter system
for rendering styles (iOS, Material3, Fluent2, Minimal). It supports smooth spring and cubic
easing animations, drag-to-toggle thumb interaction, keyboard control (Space/Enter/Arrow keys),
both horizontal and vertical orientations, icon library integration, custom image support, and
full data-binding through IBeepComponent with boolean, string, and int coercion.
The control is implemented as a partial class split across nine files, with the core fields
residing in BeepSwitch.Core.cs and the painter system delegating all visual
rendering through ISwitchPainter implementations selected by BeepControlStyle.
Key Features
- Pluggable Painter System:
ISwitchPainter(iOSSwitchPainter, Material3SwitchPainter, Fluent2SwitchPainter, MinimalSwitchPainter) viaSwitchPainterFactory.CreatePainter(ControlStyle). - Spring & Cubic Animations: Spring easing for Apple-style control styles (iOS15, MacOSBigSur, Apple) and cubic ease-out for others. 16ms timer at painter-specified frame rate.
- Drag-to-Toggle: Drag the thumb along the track; progress is computed from mouse delta / total track distance. Snaps to nearest state on release.
- Dual Orientation: Horizontal (L→R thumb travel) and Vertical (T→B)
via
SwitchOrientationenum. Track path generation adapts automatically. - Custom Labels: Separate
OnLabel/OffLabelstrings drawn at opposite ends by the active painter. - Icon Library:
OnIconName/OffIconNameresolve fromIconsManagement.SvgsUI. Convenience presets:UseCheckmarkIcons(),UsePowerIcons(),UseLightIcons(). - Legacy Images:
OnImagePath/OffImagePathviaBeepImagefor track-background images withFileNameEditorsupport. - Keyboard: Space/Enter toggle, Left/Right set false/true, Up/Down set
true/false.
IsInputKeyoverride enables focus and processing. - Focus Ring: Drawn around the track when
Focused && ShowFocusCues. Colour fromSwitchThemeHelpers.GetFocusRingColor. - Hit Areas: Track, thumb, OnLabel, and OffLabel click regions registered via BaseControl hit-area system for structural navigation.
- Data Binding:
SetValue(bool/string/int),GetValue,ClearValue,HasFilterValue,ToFilter,RefreshBinding,Oldvaluetracking.
Properties (13 total)
| Property | Type | Default | Category | Description |
|---|---|---|---|---|
Checked | bool | false | Behavior | Switch state: On (true) or Off (false). Triggers animation + CheckedChanged event. |
Orientation | SwitchOrientation | Horizontal | Appearance | Layout direction: Horizontal or Vertical. Invalidates on change. |
OnLabel | string | "On" | Appearance | Label text displayed on the ON side of the track. Invalidates on change. |
OffLabel | string | "Off" | Appearance | Label text displayed on the OFF side of the track. Invalidates on change. |
OnImagePath | string? | null | Appearance | File path (SVG/PNG/JPG) for ON-state track image. FileNameEditor at design time. |
OffImagePath | string? | null | Appearance | File path for OFF-state track image. FileNameEditor at design time. |
OnIconName | string | "" | Appearance | Icon library name for ON state (e.g. "check", "power", "lightbulb"). Resolved via IconsManagement.SvgsUI. |
OffIconName | string | "" | Appearance | Icon library name for OFF state (e.g. "close", "power_off", "lightbulb_outline"). |
DragToToggleEnabled | bool | true | Behavior | Enable drag-to-toggle. When true, thumb can be dragged along the track to change state. |
BoundProperty | string | "Checked" | Data | Property name for data-binding resolution (IBeepComponent). |
DataSourceProperty | string | null | Data | Property name on DataContext to bind to. Used by RefreshBinding(). |
LinkedProperty | string | null | Data | Linked property name for advanced binding scenarios. |
Oldvalue | object | null | Data | Previous Checked value before the last SetValue call. Read-only. Tracks state transitions. |
Methods
| Method | Visibility | Signature | Description |
|---|---|---|---|
UseCheckmarkIcons | public | void UseCheckmarkIcons() | Sets OnIconName="check", OffIconName="close". Convenience preset. |
UsePowerIcons | public | void UsePowerIcons() | Sets OnIconName="power", OffIconName="power_off". |
UseLightIcons | public | void UseLightIcons() | Sets OnIconName="lightbulb", OffIconName="lightbulb_outline". |
SetValue | public | void SetValue(object value) | Sets Checked from bool, string ("true"/"1"/"on"/"yes"), or int (≠0). Tracks Oldvalue. IBeepComponent. |
GetValue | public | object GetValue() | Returns current bool Checked value as object. IBeepComponent. |
ClearValue | public | void ClearValue() | Sets Checked=false, tracks Oldvalue. IBeepComponent. |
HasFilterValue | public | bool HasFilterValue() | Always returns true (switch has a definitive boolean value). |
ToFilter | public | AppFilter ToFilter() | Creates equality AppFilter: FieldName=BoundProperty, FilterValue=Checked.ToString(), Operator="=". |
RefreshBinding | public | void RefreshBinding() | Reads DataSourceProperty from DataContext via reflection and calls SetValue. |
ApplyTheme | public | void ApplyTheme() | Applies SwitchFontHelpers font theme, reinitialises painter, updates metrics, invalidates. |
AnimateToggle | private | void AnimateToggle(bool newState) | Starts spring or cubic animation: sets up 16ms timer, computes steps from painter.GetAnimationDuration(), interpolates _animProgress (0.0-1.0). |
UpdateMetrics | private | void UpdateMetrics() | Sets _metrics.AnimationProgress, calls painter.CalculateLayout(this, _metrics). Called before every paint. |
Toggle | private | void Toggle() | Checked = !Checked. Called from hit-area callbacks. |
SetChecked | private | void SetChecked(bool) | Checked = value. Called from hit-area label click callbacks. |
InitializePainter | private | void InitializePainter() | _painter = SwitchPainterFactory.CreatePainter(ControlStyle, this). |
GetCurrentState | private | SwitchState GetCurrentState() | Resolves SwitchState (On/Off × Normal/Hover/Pressed/Disabled/Focused) from Checked, animProgress, Enabled, IsPressed, IsHovered, Focused. |
Events
| Event | Args Type | Fire Point | Description |
|---|---|---|---|
CheckedChanged | EventHandler | Checked setter, after value change detected | Raised whenever the Checked property value changes. Fired before animation completes (animation starts first). |
Animation System
The AnimateToggle(bool newState) method manages smooth transitions:
- Duration: Retrieved from
_painter.GetAnimationDuration()(typically ~250ms). - Steps:
Math.Max(1, duration / 16)— one step per ~16ms frame. - Easing: Spring easing (
EaseOutSpring) for Apple styles (iOS15, MacOSBigSur, Apple); cubic ease-out (EaseOutCubic) for all other styles. - Interpolation:
_animProgressranges 0.0 (Off thumb position) to 1.0 (On thumb position). For newState=false,_animProgress = 1.0f - easedT. - Fallback: When
_painter == null, skips animation entirely — sets_checked = newStateand_animProgress = targetProgressinstantly. - Cancellation: Starting a new animation stops any current one via timer disposal before creating a new timer.
- Spring Formula:
Math.Pow(2, -10*t) * Math.Sin((t - s) * (2*PI) / p) + 1with p=0.4, s=p/4. Produces an overshoot-and-settle effect. - Cubic Formula:
1 - Math.Pow(1 - t, 3). Smooth deceleration.
Painter Styles
| ControlStyle | Painter | Animation | Description |
|---|---|---|---|
iOS15 | iOSSwitchPainter | Spring | iOS-style pill switch with flat thumb, rounded track caps, subtle shadow. |
MacOSBigSur | iOSSwitchPainter | Spring | macOS Big Sur style — similar to iOS but with larger proportions. |
Apple | iOSSwitchPainter | Spring | Generic Apple-style switch appearance. |
Material3 | Material3SwitchPainter | Cubic | Material Design 3: state-layer colour, circular thumb, track with opacity states. |
Fluent2 | Fluent2SwitchPainter | Cubic | Microsoft Fluent 2: rounded rectangle thumb, precise colour transitions. |
| (default) | MinimalSwitchPainter | Cubic | Clean minimal style with flat colours and simple capsule shape. |
The factory is in SwitchPainterFactory.CreatePainter(BeepControlStyle style, BeepSwitch owner).
Painters implement ISwitchPainter with five methods: CalculateLayout,
PaintTrack, PaintThumb, PaintLabels, and GetAnimationDuration.
Drag-to-Toggle
When DragToToggleEnabled = true, the user can drag the thumb to change state:
- OnMouseDown: If the click is within
_metrics.ThumbCurrentRect, sets_dragging = trueand records_dragStartX. - OnMouseMove: While dragging, computes
currentDistance = e.X - _dragStartX, thenprogress = currentDistance / totalDistance(totalDistance = ThumbOnRect.X - ThumbOffRect.X). Adjusts_animProgressby adding/subtracting progress from the current state. Clamps_animProgressto [0.0, 1.0]. CallsUpdateMetrics()andInvalidate()on every mouse move for real-time visual feedback. - OnMouseUp: If
_animProgress > 0.5f, setsChecked = true; otherwiseChecked = false. If the state didn't change, callsAnimateToggle(_checked)to snap the thumb back into place with animation.
This provides smooth, physics-feel dragging on a linear track, similar to mobile OS toggle switches.
Theming
BeepSwitch theme integration is handled through:
SwitchFontHelpers.ApplyFontTheme(ControlStyle)— applies the correct font for the current BeepControlStyle from the active theme.SwitchPainterFactory.CreatePainter(ControlStyle, this)— re-creates the painter to match the current ControlStyle, which reflects the theme.SwitchThemeHelpers.GetFocusRingColor(theme, useThemeColors)— resolves the focus ring colour from the current theme. ReturnsColor.Transparentif no focus ring should be drawn.UpdateMetrics()— recalculates all layout rectangles (track, thumb positions, label rects) from the new painter and control dimensions.Invalidate()— forces a complete repaint after theme changes.
The track path is generated by CreateTrackPath() which adapts to the current
Orientation. In Horizontal mode, arc radii equal TrackHeight / 2
(horizontal pill shape). In Vertical mode, arc radii equal TrackWidth / 2
(vertical pill shape).
The SwitchMetrics model (in Models/SwitchMetrics.cs) stores:
TrackRect, ThumbOnRect, ThumbOffRect,
ThumbCurrentRect, OnLabelRect, OffLabelRect,
AnimationProgress, TrackHeight, TrackWidth.
Examples
Basic Toggle Switch
var toggle = new BeepSwitch
{
Checked = false, OnLabel = "Enabled", OffLabel = "Disabled",
Width = 120, Height = 50
};
toggle.CheckedChanged += (s, e) =>
Console.WriteLine($"Switch: {toggle.Checked}");
Vertical Orientation
var vert = new BeepSwitch
{
Orientation = SwitchOrientation.Vertical,
OnLabel = "ON", OffLabel = "OFF",
Width = 50, Height = 120
};
Icon Library Presets
var iconSwitch = new BeepSwitch { Width = 60, Height = 40 };
iconSwitch.UseCheckmarkIcons(); // check + close
iconSwitch.UsePowerIcons(); // power + power_off
iconSwitch.UseLightIcons(); // lightbulb + lightbulb_outline
Custom Icons from Library
var custom = new BeepSwitch
{
OnIconName = "volume_up", OffIconName = "volume_mute",
Width = 60, Height = 40
};
Data Binding
var bound = new BeepSwitch { BoundProperty = "Checked", DataSourceProperty = "IsActive" };
bound.SetValue(true);
bool val = (bool)bound.GetValue();
bound.ClearValue();
bound.RefreshBinding();
Drag-to-Toggle
var drag = new BeepSwitch
{
DragToToggleEnabled = true, // default
OnLabel = "Slide", OffLabel = "Slide",
Width = 150, Height = 50
};
Custom Image as Track Background
var img = new BeepSwitch
{
OnImagePath = @"C:\img\on_bg.png",
OffImagePath = @"C:\img\off_bg.png",
Width = 120, Height = 50
};
Setting from String Values
var switch2 = new BeepSwitch();
switch2.SetValue("true"); // checked
switch2.SetValue("1"); // checked
switch2.SetValue("yes"); // checked
switch2.SetValue("on"); // checked
switch2.SetValue(0); // unchecked
switch2.SetValue(1); // checked
Layout Metrics System
The SwitchMetrics model stores all calculated layout rectangles and
is updated before every paint via UpdateMetrics():
TrackRect— the capsule track area, inset by padding from the client rectangle.ThumbOffRect— the thumb's position when the switch is off (0.0 progress).ThumbOnRect— the thumb's position when the switch is on (1.0 progress).ThumbCurrentRect— linearly interpolated between Off and On based onAnimationProgress. Used for hit-testing and rendering.OnLabelRect/OffLabelRect— label bounding boxes at the track ends.AnimationProgress— current 0.0-1.0 float, set by animation or drag.TrackHeight/TrackWidth— dimension helpers for path generation.
The track path (CreateTrackPath()) generates a horizontal pill (arc radius =
TrackHeight/2) or vertical pill (arc radius = TrackWidth/2) depending on Orientation.
Architecture
| File | Lines | Responsibility |
|---|---|---|
BeepSwitch.cs | ~252 | Main class (partial), legacy drawing methods (DrawHorizontalSwitch_Legacy, DrawVerticalSwitch_Legacy, GetCapsulePath_Legacy), Dispose (disposes _onBeepImage, _offBeepImage). Note: all modern fields/properties/methods are in the partial files below. |
BeepSwitch.Core.cs | ~101 | Core: all private fields (_checked, _orientation, _onLabel, _offLabel, image paths, BeepImage instances, _onIconName, _offIconName, SwitchMetrics _metrics, ISwitchPainter _painter, Timer _animTimer, _animProgress, _animating, _dragging, _dragStartX, _dragToToggleEnabled), constructor (sets ControlStyles, default size 120x50, initialises BeepImage instances, calls InitializePainter, defaults to iOS15 style), InitializePainter. |
BeepSwitch.Properties.cs | ~233 | Properties: Checked (triggers AnimateToggle + OnCheckedChanged), Orientation, OnLabel, OffLabel, OnImagePath, OffImagePath, OnIconName, OffIconName, DragToToggleEnabled, BoundProperty, DataSourceProperty, LinkedProperty. Icon convenience methods (UseCheckmarkIcons, UsePowerIcons, UseLightIcons). Event raiser OnCheckedChanged. |
BeepSwitch.Drawing.cs | ~138 | Drawing: DrawContent, Draw, Paint (central: UpdateMetrics, CreateTrackPath, painter.PaintTrack/PaintThumb/PaintLabels, DrawFocusRing), CreateTrackPath (horizontal/vertical pill path generation), GetCurrentState (SwitchState resolution), DrawFocusRing. |
BeepSwitch.Interaction.cs | ~149 | Input: OnMouseClick, OnMouseDown (drag start), OnMouseMove (drag tracking or cursor change), OnMouseUp (commit drag or snap-back), OnMouseLeave (reset cursor), IsInputKey (Space/Enter/Left/Right), OnKeyDown (Space/Enter toggle, arrows directional set), OnEnabledChanged, OnGotFocus, OnLostFocus. |
BeepSwitch.DataBinding.cs | ~111 | Data: SetValue (bool/string/int coercion, tracks Oldvalue), GetValue, ClearValue, HasFilterValue (always true), ToFilter (AppFilter), RefreshBinding (reflection from DataContext), Oldvalue property. |
BeepSwitch.Layout.cs | ~58 | Layout: OnSizeChanged, UpdateMetricsAndHitAreas, RegisterHitAreas (track, thumb, onLabel, offLabel), Toggle, SetChecked. |
BeepSwitch.Animation.cs | ~94 | Animation: AnimateToggle, EaseOutCubic, EaseOutSpring, UpdateMetrics. |
BeepSwitch.Theme.cs | ~26 | Theme: ApplyTheme (SwitchFontHelpers, reinitialise painter, update metrics, invalidate). |
Helpers/ISwitchPainter.cs | ~30 | Interface: CalculateLayout, PaintTrack, PaintThumb, PaintLabels, GetAnimationDuration. |
Helpers/SwitchPainterFactory.cs | ~40 | Factory: static CreatePainter(BeepControlStyle, BeepSwitch). |
Helpers/Painters/iOSSwitchPainter.cs | ~100 | iOS style: pill track, flat thumb, spring animation. |
Helpers/Painters/Material3SwitchPainter.cs | ~80 | Material3: state-layer colour, opacity transitions. |
Helpers/Painters/Fluent2SwitchPainter.cs | ~80 | Fluent2: rounded-rect thumb, precise colours. |
Helpers/Painters/MinimalSwitchPainter.cs | ~80 | Minimal: clean, flat colours. |
Helpers/SwitchThemeHelpers.cs | ~30 | Focus ring colour resolution. |
Helpers/SwitchFontHelpers.cs | ~30 | Font theme application. |
Helpers/SwitchStyleHelpers.cs | ~30 | Style metrics. |
Helpers/SwitchIconHelpers.cs | ~30 | Default icon paths. |
Models/SwitchState.cs | ~20 | Enum: Off_Normal, Off_Hover, Off_Pressed, Off_Disabled, Off_Focused, On_* equivalents (10 values). |
Models/SwitchMetrics.cs | ~30 | Model: TrackRect, ThumbOn/Off/CurrentRect, On/OffLabelRect, AnimationProgress, TrackHeight/Width. |
Models/SwitchOrientation.cs | ~15 | Enum: Horizontal, Vertical. |
Models/SwitchStyleConfig.cs | ~30 | Style configuration presets. |
Models/SwitchColorConfig.cs | ~30 | Colour config per style per state. |