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;
}
}