#nullable enable namespace Terminal.Gui; /// Shows a check box that can be cycled between three states. public class CheckBox : View { /// /// Gets or sets the default Highlight Style. /// [SerializableConfigurationProperty (Scope = typeof (ThemeScope))] public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.PressedOutside | HighlightStyle.Pressed | HighlightStyle.Hover; /// /// Initializes a new instance of . /// public CheckBox () { Width = Dim.Auto (DimAutoStyle.Text); Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1); CanFocus = true; // Things this view knows how to do AddCommand (Command.Accept, AdvanceCheckState); AddCommand (Command.HotKey, AdvanceCheckState); // Default keybindings for this view KeyBindings.Add (Key.Space, Command.Accept); TitleChanged += Checkbox_TitleChanged; HighlightStyle = DefaultHighlightStyle; MouseClick += CheckBox_MouseClick; } private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e) { e.Handled = AdvanceCheckState () == true; } private void Checkbox_TitleChanged (object? sender, EventArgs e) { base.Text = e.CurrentValue; 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; } private bool _allowNone = false; /// /// 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 ConfigurationManager.Glyphs.CheckStateNone character (☒). /// /// /// If , the /// will display the ConfigurationManager.Glyphs.CheckStateUnChecked character (☐). /// /// /// If , the /// will display the ConfigurationManager.Glyphs.CheckStateChecked character (☑). /// /// public CheckState CheckedState { get => _checkedState; set { if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone)) { return; } _checkedState = value; UpdateTextFormatterText (); OnResizeNeeded (); } } /// /// Advances to the next value. Invokes the cancelable event. /// /// /// /// If the event was canceled. /// /// /// Cycles through the states , , and . /// /// /// If the event is not canceled, the will be updated and the event will be raised. /// /// public bool? AdvanceCheckState () { CheckState oldValue = CheckedState; CancelEventArgs e = new (in _checkedState, ref oldValue); switch (CheckedState) { case CheckState.None: e.NewValue = CheckState.Checked; break; case CheckState.Checked: e.NewValue = CheckState.UnChecked; break; case CheckState.UnChecked: if (AllowCheckStateNone) { e.NewValue = CheckState.None; } else { e.NewValue = CheckState.Checked; } break; } CheckedStateChanging?.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 Accept event is fired. if (OnAccept () == true) { return true; } CheckedState = e.NewValue; return true; } /// Raised when the state is changing. /// /// /// This event can be cancelled. If cancelled, the will not change its state. /// /// public event EventHandler>? CheckedStateChanging; /// protected override void UpdateTextFormatterText () { base.UpdateTextFormatterText (); switch (TextAlignment) { case Alignment.Start: case Alignment.Center: case Alignment.Fill: TextFormatter.Text = $"{GetCheckedGlyph ()} {Text}"; break; case Alignment.End: TextFormatter.Text = $"{Text} {GetCheckedGlyph ()}"; break; } } private Rune GetCheckedGlyph () { return CheckedState switch { CheckState.Checked => Glyphs.CheckStateChecked, CheckState.UnChecked => Glyphs.CheckStateUnChecked, CheckState.None => Glyphs.CheckStateNone, _ => throw new ArgumentOutOfRangeException () }; } }