#nullable enable
namespace Terminal.Gui;
/// Shows a check box that can be cycled between two or 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, 1);
CanFocus = true;
// Select (Space key and single-click) - Advance state and raise Select event - DO NOT raise Accept
AddCommand (Command.Select, AdvanceAndSelect);
// Hotkey - Advance state and raise Select event - DO NOT raise Accept
AddCommand (Command.HotKey, AdvanceAndSelect);
// Accept (Enter key) - Raise Accept event - DO NOT advance state
AddCommand (Command.Accept, RaiseAccepting);
TitleChanged += Checkbox_TitleChanged;
HighlightStyle = DefaultHighlightStyle;
}
private bool? AdvanceAndSelect (CommandContext ctx)
{
bool? cancelled = AdvanceCheckState ();
if (cancelled is true)
{
return true;
}
if (RaiseSelecting (ctx) is true)
{
return true;
}
return ctx.Command == Command.HotKey ? cancelled : cancelled is false;
}
private void Checkbox_TitleChanged (object? sender, EventArgs e)
{
base.Text = e.CurrentValue;
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 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 => 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;
}
CancelEventArgs e = new (in _checkedState, ref value);
if (OnCheckedStateChanging (e))
{
return true;
}
CheckedStateChanging?.Invoke (this, e);
if (e.Cancel)
{
return e.Cancel;
}
_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 cahnge can be cancelled by setting the args.Cancel to .
///
///
protected virtual bool OnCheckedStateChanging (CancelEventArgs 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;
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;
}
bool? cancelled = ChangeCheckedState (e.NewValue);
return cancelled;
}
///
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 ()
};
}
}