#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 (Dim.DimAutoStyle.Text);
Height = 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 ();
}
}
///
public override bool OnEnter (View view)
{
Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
return base.OnEnter (view);
}
/// 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;
}
///
public override void PositionCursor () { Move (0, 0); }
/// 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 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 (Title) || Frame.Width <= 2)
{
return Text;
}
return Text [..Math.Min (Frame.Width - 2, Text.GetRuneCount ())];
}
}