namespace Terminal.Gui.Views; /// Shows a checkbox that can be cycled between two or three states. /// /// /// is used to display radio button style glyphs (●) instead of checkbox style glyphs (☑). /// /// public class CheckBox : View { private static MouseState _defaultHighlightStates = MouseState.PressedOutside | MouseState.Pressed | MouseState.In; // Resources/config.json overrides /// /// Gets or sets the default Highlight Style. /// [ConfigurationProperty (Scope = typeof (ThemeScope))] public static MouseState DefaultHighlightStates { get => _defaultHighlightStates; set => _defaultHighlightStates = value; } /// /// Initializes a new instance of . /// public CheckBox () { Width = Dim.Auto (DimAutoStyle.Text); Height = Dim.Auto (DimAutoStyle.Text, 1); CanFocus = true; // Activate (Space key and single-click) - Raise Activate event and Advance // - DO NOT raise Accept // - DO NOT SetFocus AddCommand (Command.Activate, ActivateAndAdvance); // Accept (Enter key and double-click) - Raise Accept event // - DO NOT advance state // The default Accept handler does that. MouseBindings.Add (MouseFlags.Button1DoubleClicked, Command.Accept); TitleChanged += Checkbox_TitleChanged; HighlightStates = DefaultHighlightStates; } /// protected override bool OnHandlingHotKey (CommandEventArgs args) { // Invoke Activate on ourselves if (InvokeCommand (Command.Activate, args.Context) is true) { // Default behavior for View is to set Focus on hotkey. We need to return // true here to indicate Activate was handled. That will prevent the default // behavior from setting focus, so we do it here. SetFocus (); return true; } return base.OnHandlingHotKey (args); } private bool? ActivateAndAdvance (ICommandContext? commandContext) { if (RaiseActivating (commandContext) is true) { return true; } bool? cancelled = AdvanceCheckState (); if (cancelled is true) { return true; } return commandContext?.Command == Command.HotKey ? cancelled : cancelled is false; } private void Checkbox_TitleChanged (object? sender, EventArgs e) { base.Text = e.Value; TextFormatter.HotKeySpecifier = HotKeySpecifier; } /// public override string Text { get => Title; set => base.Text = Title = value; } /// public override Rune HotKeySpecifier { get => base.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; } private bool _allowNone; /// /// If allows to be . The default is /// . /// public bool AllowCheckStateNone { get => _allowNone; set { if (_allowNone == value) { return; } _allowNone = value; if (CheckedState == CheckState.None) { CheckedState = CheckState.UnChecked; } } } private CheckState _checkedState = CheckState.UnChecked; /// /// The state of the . /// /// /// /// If is and , the /// /// will display the Glyphs.CheckStateNone character (☒). /// /// /// If , the /// will display the Glyphs.CheckStateUnChecked character (☐). /// /// /// If , the /// will display the Glyphs.CheckStateChecked character (☑). /// /// public CheckState CheckedState { get => _checkedState; set => ChangeCheckedState (value); } /// /// INTERNAL Sets CheckedState. /// /// /// /// if state change was canceled, if the state changed, and /// if the state was not changed for some other reason. /// private bool? ChangeCheckedState (CheckState value) { if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone)) { return null; } ResultEventArgs e = new (value); if (OnCheckedStateChanging (e)) { return true; } CheckedStateChanging?.Invoke (this, e); if (e.Handled) { return e.Handled; } _checkedState = value; UpdateTextFormatterText (); SetNeedsLayout (); EventArgs args = new (in _checkedState); OnCheckedStateChanged (args); CheckedStateChanged?.Invoke (this, args); return false; } /// Called when the state is changing. /// /// /// The state change can be cancelled by setting the args.Cancel to . /// /// protected virtual bool OnCheckedStateChanging (ResultEventArgs args) { return false; } /// Raised when the state is changing. /// /// /// This event can be cancelled. If cancelled, the will not change its state. /// /// public event EventHandler>? CheckedStateChanging; /// Called when the state has changed. protected virtual void OnCheckedStateChanged (EventArgs args) { } /// Raised when the state has changed. public event EventHandler>? CheckedStateChanged; /// /// Advances to the next value. Invokes the cancelable /// event. /// /// /// /// Cycles through the states , , and /// . /// /// /// If the event is not canceled, the will be updated /// and the event will be raised. /// /// /// /// if state change was canceled, if the state changed, and /// if the state was not changed for some other reason. /// public bool? AdvanceCheckState () { CheckState oldValue = CheckedState; ResultEventArgs e = new (oldValue); switch (CheckedState) { case CheckState.None: e.Result = CheckState.Checked; break; case CheckState.Checked: e.Result = CheckState.UnChecked; break; case CheckState.UnChecked: if (AllowCheckStateNone) { e.Result = CheckState.None; } else { e.Result = CheckState.Checked; } break; } bool? cancelled = ChangeCheckedState (e.Result); return cancelled; } /// protected override bool OnClearingViewport () { SetAttributeForRole (HasFocus ? VisualRole.Focus : VisualRole.Normal); return base.OnClearingViewport (); } /// protected override void UpdateTextFormatterText () { base.UpdateTextFormatterText (); Rune glyph = RadioStyle ? GetRadioGlyph () : GetCheckGlyph (); switch (TextAlignment) { case Alignment.Start: case Alignment.Center: case Alignment.Fill: TextFormatter.Text = $"{glyph} {Text}"; break; case Alignment.End: TextFormatter.Text = $"{Text} {glyph}"; break; } } private Rune GetCheckGlyph () { return CheckedState switch { CheckState.Checked => Glyphs.CheckStateChecked, CheckState.UnChecked => Glyphs.CheckStateUnChecked, CheckState.None => Glyphs.CheckStateNone, _ => throw new ArgumentOutOfRangeException () }; } /// /// If , the will display radio button style glyphs (●) instead of /// checkbox style glyphs (☑). /// public bool RadioStyle { get; set; } private Rune GetRadioGlyph () { return CheckedState switch { CheckState.Checked => Glyphs.Selected, CheckState.UnChecked => Glyphs.UnSelected, CheckState.None => Glyphs.Dot, _ => throw new ArgumentOutOfRangeException () }; } }