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; HotKeySpecifier = (Rune)'_'; // Ensures a height of 1 if AutoSize is set to false Height = 1; CanFocus = true; AutoSize = true; // Things this view knows how to do AddCommand (Command.ToggleChecked, () => ToggleChecked ()); AddCommand ( Command.Accept, () => { if (!HasFocus) { SetFocus (); } ToggleChecked (); return true; } ); // Default keybindings for this view KeyBindings.Add (Key.Space, Command.ToggleChecked); } /// /// 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 (); } } /// public override Key HotKey { get => base.HotKey; set { if (value is null) { throw new ArgumentException (nameof (value)); } Key prev = base.HotKey; if (prev != value) { base.HotKey = TextFormatter.HotKey = value; // Also add Alt+HotKey if (prev != Key.Empty && KeyBindings.TryGet (prev.WithAlt, out _)) { if (value.KeyCode == KeyCode.Null) { KeyBindings.Remove (prev.WithAlt); } else { KeyBindings.Replace (prev.WithAlt, value.WithAlt); } } else if (value != Key.Empty) { KeyBindings.Add (value.WithAlt, Command.Accept); } } } } /// public override bool MouseEvent (MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) { return false; } ToggleChecked (); return true; } /// public override bool OnEnter (View view) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); return base.OnEnter (view); } /// Called when the property changes. Invokes the event. public virtual void OnToggled (ToggleEventArgs e) { Toggled?.Invoke (this, e); } /// public override void PositionCursor () { Move (0, 0); } /// Toggled event, raised when the is toggled. /// /// Client code can hook up to this event, it is raised when the is activated either with /// the mouse or the keyboard. The passed bool contains the previous state. /// public event EventHandler Toggled; /// protected override void UpdateTextFormatterText () { switch (TextAlignment) { case TextAlignment.Left: case TextAlignment.Centered: case TextAlignment.Justified: TextFormatter.Text = $"{GetCheckedState ()} {GetFormatterText ()}"; break; case TextAlignment.Right: TextFormatter.Text = $"{GetFormatterText ()} {GetCheckedState ()}"; break; } } private Rune GetCheckedState () { return Checked switch { true => _charChecked, false => _charUnChecked, var _ => _charNullChecked }; } private string GetFormatterText () { if (AutoSize || string.IsNullOrEmpty (Text) || Frame.Width <= 2) { return Text; } return Text [..Math.Min (Frame.Width - 2, Text.GetRuneCount ())]; } private bool ToggleChecked () { if (!HasFocus) { SetFocus (); } bool? previousChecked = Checked; if (AllowNullChecked) { switch (previousChecked) { case null: Checked = true; break; case true: Checked = false; break; case false: Checked = null; break; } } else { Checked = !Checked; } OnToggled (new ToggleEventArgs (previousChecked, Checked)); SetNeedsDisplay (); return true; } }