#nullable enable namespace Terminal.Gui; /// The shows an on/off toggle that the user can set public class CheckBox : View { private readonly Rune _charChecked; private readonly Rune _charNullChecked; private readonly Rune _charUnChecked; private bool _allowNullChecked; private bool? _checked = false; /// /// Initializes a new instance of based on the given text, using /// layout. /// public CheckBox () { _charNullChecked = Glyphs.NullChecked; _charChecked = Glyphs.Checked; _charUnChecked = Glyphs.UnChecked; Width = Dim.Auto (DimAutoStyle.Text); Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1); CanFocus = true; // Things this view knows how to do AddCommand (Command.Accept, OnToggled); AddCommand (Command.HotKey, OnToggled); // Default keybindings for this view KeyBindings.Add (Key.Space, Command.Accept); TitleChanged += Checkbox_TitleChanged; HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed; MouseClick += CheckBox_MouseClick; } private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e) { e.Handled = OnToggled () == true; } private void Checkbox_TitleChanged (object? sender, StateEventArgs e) { base.Text = e.NewValue; TextFormatter.HotKeySpecifier = HotKeySpecifier; } /// public override string Text { get => base.Title; set => base.Text = base.Title = value; } /// public override Rune HotKeySpecifier { get => base.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value; } /// /// If allows to be null, true or false. If /// only allows to be true or false. /// public bool AllowNullChecked { get => _allowNullChecked; set { _allowNullChecked = value; Checked ??= false; } } /// The state of the public bool? Checked { get => _checked; set { if (value is null && !AllowNullChecked) { return; } _checked = value; UpdateTextFormatterText (); OnResizeNeeded (); } } /// Called when the property changes. Invokes the event. /// /// /// If the event was canceled. public bool? OnToggled () { StateEventArgs e = new (Checked, false); if (AllowNullChecked) { switch (Checked) { case null: e.NewValue = true; break; case true: e.NewValue = false; break; case false: e.NewValue = null; break; } } else { e.NewValue = !Checked; } Toggled?.Invoke (this, e); if (e.Cancel) { return e.Cancel; } // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired. if (OnAccept () == true) { return true; } Checked = e.NewValue; return true; } /// Toggled event, raised when the is toggled. /// /// /// This event can be cancelled. If cancelled, the will not change its state. /// /// public event EventHandler>? Toggled; /// protected override void UpdateTextFormatterText () { switch (TextAlignment) { case Alignment.Start: case Alignment.Center: case Alignment.Fill: TextFormatter.Text = $"{GetCheckedState ()} {Text}"; break; case Alignment.End: TextFormatter.Text = $"{Text} {GetCheckedState ()}"; break; } } private Rune GetCheckedState () { return Checked switch { true => _charChecked, false => _charUnChecked, var _ => _charNullChecked }; } }