Namespace: Beep.Skia.Controls — Inherits: CheckBox → MaterialControl → SkiaComponent

Overview

The CheckBox, CheckBoxGroup, and RadioGroup controls provide Material Design 3.0 toggle selection, rendered entirely with SkiaSharp. CheckBox is an independent toggle with a square checkmark icon that animates between checked and unchecked states. CheckBoxGroup manages a collection of checkboxes with multi-select support. RadioGroup enforces single-selection among a set of radio buttons with circular indicators.

All toggle controls follow MD3 design: the check/radio icon animates with a 200ms easing curve, ripple effects emanate from the pointer contact point, and state layers provide hover/focus/pressed feedback. Labels are rendered using BodyLarge typography with configurable text positioning relative to the icon.

Animated Check/Uncheck

The checkmark and radio fill animate with a smooth scale+fade transition managed by the Material motion system.

Indeterminate State

CheckBox supports a third state (indeterminate) drawn as a horizontal dash, useful for parent checkboxes with mixed child states.

Group Coordination

RadioGroup enforces mutual exclusion. CheckBoxGroup tracks collective state with SelectedIndices.

Keyboard Navigation

Tab to focus, Space to toggle. Arrow keys navigate between items in a group.

CheckBox Properties

PropertyTypeDescription
IsChecked bool? The toggle state. true = checked, false = unchecked, null = indeterminate (dash icon). Default is false.
Text string Label text displayed to the right of the check box icon. Can be null for icon-only checkboxes.
CheckBoxStyle CheckBoxStyle Enum: Square (default, standard checkbox) or Toggle (switch-style with a pill-shaped thumb).
IconSize float Size of the check box icon square. Default is 18f per MD3 spec.
LabelPosition LabelPosition Enum: Right (default) or Left. Controls whether the label text appears to the right or left of the icon.
IsThreeState bool When true, clicking cycles through false → true → null (indeterminate) → false. Default is false.
IsHovered bool Read-only. Whether the mouse is over the touch target (icon + label area).

CheckBoxStyle.Toggle (Switch) Properties

When CheckBoxStyle is set to Toggle, the control renders as a Material Design switch with additional properties:

PropertyTypeDescription
ToggleTrackWidthfloatWidth of the switch track. Default is 52f.
ToggleTrackHeightfloatHeight of the switch track. Default is 32f.
ToggleThumbSizefloatDiameter of the circular thumb. Default is 24f.

CheckBoxGroup Properties

CheckBoxGroup is a container control that manages a collection of CheckBox children:

PropertyTypeDescription
Items List<CheckBox> The child checkboxes managed by this group. Each maintains its own IsChecked state independently.
SelectedIndices HashSet<int> Read-only. The zero-based indices of currently checked items. Updated automatically when any child checkbox toggles.
LayoutDirection LayoutDirection Enum: Vertical (default) or Horizontal. Controls how child checkboxes are arranged.
ItemSpacing float Gap between child checkboxes. Default is 8f.

RadioGroup Properties

RadioGroup manages a mutually-exclusive set of radio buttons. Unlike CheckBoxGroup, only one item can be selected at a time.

PropertyTypeDescription
Items List<RadioItem> The radio button items. Each RadioItem has Text, Value, and optional Icon properties. The radio circle is drawn by the group, not per-item.
SelectedIndex int Index of the selected radio button. -1 when nothing is selected. Setting this updates all child radio circles.
SelectedValue object Read-only. The Value property of the selected RadioItem, or null.
RadioDiameter float Diameter of the radio circle. Default is 20f.
LayoutDirection LayoutDirection Enum: Vertical (default) or Horizontal.

Events

EventOwnerSignatureDescription
CheckedChanged CheckBox EventHandler<bool?> Raised when IsChecked changes. The argument is the new value (true, false, or null for indeterminate).
SelectedIndicesChanged CheckBoxGroup EventHandler<HashSet<int>> Raised when any child checkbox toggles. The argument is the updated SelectedIndices set.
SelectedIndexChanged RadioGroup EventHandler<int> Raised when the selected radio button changes. The argument is the new SelectedIndex.

Code Example

Basic CheckBox and Toggle Switch

var darkModeCheck = new CheckBox
{
    Text = "Enable Dark Mode",
    IsChecked = false,
    X = 40, Y = 40,
    Width = 200, Height = 40
};

darkModeCheck.Initialize();
darkModeCheck.CheckedChanged += (s, state) =>
    Console.WriteLine($"Dark mode: {state}");

var notificationsToggle = new CheckBox
{
    Text = "Push Notifications",
    CheckBoxStyle = CheckBoxStyle.Toggle,
    IsChecked = true,
    X = 40, Y = 90,
    Width = 220, Height = 40
};

notificationsToggle.Initialize();

drawingManager.AddComponent(darkModeCheck);
drawingManager.AddComponent(notificationsToggle);
drawingManager.RequestRedraw();

Three-State CheckBox (Indeterminate)

var child1 = new CheckBox { Text = "Option A", X = 0, Y = 0 };
var child2 = new CheckBox { Text = "Option B", X = 0, Y = 0 };
var child3 = new CheckBox { Text = "Option C", X = 0, Y = 0 };

var selectAll = new CheckBox
{
    Text = "Select All",
    IsThreeState = true,
    IsChecked = false,
    X = 40, Y = 150,
    Width = 200, Height = 40
};
selectAll.Initialize();

selectAll.CheckedChanged += (s, state) =>
{
    if (state == true || state == false)
    {
        child1.IsChecked = state;
        child2.IsChecked = state;
        child3.IsChecked = state;
    }
};

Action updateParent = () =>
{
    var states = new[] { child1.IsChecked, child2.IsChecked, child3.IsChecked };
    if (states.All(c => c == true)) selectAll.IsChecked = true;
    else if (states.All(c => c == false)) selectAll.IsChecked = false;
    else selectAll.IsChecked = null;
};

child1.CheckedChanged += (s, v) => updateParent();
child2.CheckedChanged += (s, v) => updateParent();
child3.CheckedChanged += (s, v) => updateParent();

drawingManager.AddComponent(selectAll);

CheckBoxGroup with Vertical Layout

var group = new CheckBoxGroup
{
    X = 300, Y = 40,
    Width = 250,
    LayoutDirection = LayoutDirection.Vertical,
    ItemSpacing = 4f,
    Items = new List<CheckBox>
    {
        new() { Text = "Feature A: Auto-save" },
        new() { Text = "Feature B: Spell check" },
        new() { Text = "Feature C: Live preview" },
        new() { Text = "Feature D: Dark theme" },
        new() { Text = "Feature E: Compact mode" }
    }
};

foreach (var cb in group.Items) cb.Initialize();

group.Initialize();
group.SelectedIndicesChanged += (s, indices) =>
    Console.WriteLine($"Selected: {string.Join(", ", indices)}");

drawingManager.AddComponent(group);

RadioGroup

var radioGroup = new RadioGroup
{
    X = 300, Y = 280,
    Width = 250,
    LayoutDirection = LayoutDirection.Vertical,
    SelectedIndex = 0,
    Items = new List<RadioItem>
    {
        new() { Text = "Small (12px)",  Value = 12 },
        new() { Text = "Medium (16px)", Value = 16 },
        new() { Text = "Large (20px)",  Value = 20 },
        new() { Text = "Extra Large (24px)", Value = 24 }
    }
};

radioGroup.Initialize();
radioGroup.SelectedIndexChanged += (s, idx) =>
{
    int fontSize = (int)radioGroup.SelectedValue;
    Console.WriteLine($"Font size set to: {fontSize}px");
};

drawingManager.AddComponent(radioGroup);
drawingManager.RequestRedraw();

Tip

When using CheckBoxGroup or RadioGroup, add the group to the drawing manager, not individual child items. The group manages child layout, event routing, and invalidation automatically. Child coordinates are relative to the group's origin.