CheckBox.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. namespace Terminal.Gui;
  2. /// <summary>The <see cref="CheckBox"/> <see cref="View"/> shows an on/off toggle that the user can set</summary>
  3. public class CheckBox : View
  4. {
  5. private readonly Rune _charChecked;
  6. private readonly Rune _charNullChecked;
  7. private readonly Rune _charUnChecked;
  8. private bool _allowNullChecked;
  9. private bool? _checked = false;
  10. /// <summary>
  11. /// Initializes a new instance of <see cref="CheckBox"/> based on the given text, using
  12. /// <see cref="LayoutStyle.Computed"/> layout.
  13. /// </summary>
  14. public CheckBox ()
  15. {
  16. _charNullChecked = Glyphs.NullChecked;
  17. _charChecked = Glyphs.Checked;
  18. _charUnChecked = Glyphs.UnChecked;
  19. HotKeySpecifier = (Rune)'_';
  20. // Ensures a height of 1 if AutoSize is set to false
  21. Height = 1;
  22. CanFocus = true;
  23. AutoSize = true;
  24. // Things this view knows how to do
  25. AddCommand (Command.ToggleChecked, () => ToggleChecked ());
  26. AddCommand (
  27. Command.Accept,
  28. () =>
  29. {
  30. if (!HasFocus)
  31. {
  32. SetFocus ();
  33. }
  34. ToggleChecked ();
  35. return true;
  36. }
  37. );
  38. // Default keybindings for this view
  39. KeyBindings.Add (Key.Space, Command.ToggleChecked);
  40. }
  41. /// <summary>
  42. /// If <see langword="true"/> allows <see cref="Checked"/> to be null, true or false. If <see langword="false"/>
  43. /// only allows <see cref="Checked"/> to be true or false.
  44. /// </summary>
  45. public bool AllowNullChecked
  46. {
  47. get => _allowNullChecked;
  48. set
  49. {
  50. _allowNullChecked = value;
  51. Checked ??= false;
  52. }
  53. }
  54. /// <summary>The state of the <see cref="CheckBox"/></summary>
  55. public bool? Checked
  56. {
  57. get => _checked;
  58. set
  59. {
  60. if (value is null && !AllowNullChecked)
  61. {
  62. return;
  63. }
  64. _checked = value;
  65. UpdateTextFormatterText ();
  66. OnResizeNeeded ();
  67. }
  68. }
  69. /// <inheritdoc/>
  70. public override Key HotKey
  71. {
  72. get => base.HotKey;
  73. set
  74. {
  75. if (value is null)
  76. {
  77. throw new ArgumentException (nameof (value));
  78. }
  79. Key prev = base.HotKey;
  80. if (prev != value)
  81. {
  82. base.HotKey = TextFormatter.HotKey = value;
  83. // Also add Alt+HotKey
  84. if (prev != Key.Empty && KeyBindings.TryGet (prev.WithAlt, out _))
  85. {
  86. if (value.KeyCode == KeyCode.Null)
  87. {
  88. KeyBindings.Remove (prev.WithAlt);
  89. }
  90. else
  91. {
  92. KeyBindings.Replace (prev.WithAlt, value.WithAlt);
  93. }
  94. }
  95. else if (value != Key.Empty)
  96. {
  97. KeyBindings.Add (value.WithAlt, Command.Accept);
  98. }
  99. }
  100. }
  101. }
  102. /// <inheritdoc/>
  103. public override bool MouseEvent (MouseEvent me)
  104. {
  105. if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus)
  106. {
  107. return false;
  108. }
  109. ToggleChecked ();
  110. return true;
  111. }
  112. /// <inheritdoc/>
  113. public override bool OnEnter (View view)
  114. {
  115. Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
  116. return base.OnEnter (view);
  117. }
  118. /// <summary>Called when the <see cref="Checked"/> property changes. Invokes the <see cref="Toggled"/> event.</summary>
  119. public virtual void OnToggled (ToggleEventArgs e) { Toggled?.Invoke (this, e); }
  120. /// <inheritdoc/>
  121. public override void PositionCursor () { Move (0, 0); }
  122. /// <summary>Toggled event, raised when the <see cref="CheckBox"/> is toggled.</summary>
  123. /// <remarks>
  124. /// Client code can hook up to this event, it is raised when the <see cref="CheckBox"/> is activated either with
  125. /// the mouse or the keyboard. The passed <c>bool</c> contains the previous state.
  126. /// </remarks>
  127. public event EventHandler<ToggleEventArgs> Toggled;
  128. /// <inheritdoc/>
  129. protected override void UpdateTextFormatterText ()
  130. {
  131. switch (TextAlignment)
  132. {
  133. case TextAlignment.Left:
  134. case TextAlignment.Centered:
  135. case TextAlignment.Justified:
  136. TextFormatter.Text = $"{GetCheckedState ()} {GetFormatterText ()}";
  137. break;
  138. case TextAlignment.Right:
  139. TextFormatter.Text = $"{GetFormatterText ()} {GetCheckedState ()}";
  140. break;
  141. }
  142. }
  143. private Rune GetCheckedState ()
  144. {
  145. return Checked switch
  146. {
  147. true => _charChecked,
  148. false => _charUnChecked,
  149. var _ => _charNullChecked
  150. };
  151. }
  152. private string GetFormatterText ()
  153. {
  154. if (AutoSize || string.IsNullOrEmpty (Text) || Frame.Width <= 2)
  155. {
  156. return Text;
  157. }
  158. return Text [..Math.Min (Frame.Width - 2, Text.GetRuneCount ())];
  159. }
  160. private bool ToggleChecked ()
  161. {
  162. if (!HasFocus)
  163. {
  164. SetFocus ();
  165. }
  166. bool? previousChecked = Checked;
  167. if (AllowNullChecked)
  168. {
  169. switch (previousChecked)
  170. {
  171. case null:
  172. Checked = true;
  173. break;
  174. case true:
  175. Checked = false;
  176. break;
  177. case false:
  178. Checked = null;
  179. break;
  180. }
  181. }
  182. else
  183. {
  184. Checked = !Checked;
  185. }
  186. OnToggled (new ToggleEventArgs (previousChecked, Checked));
  187. SetNeedsDisplay ();
  188. return true;
  189. }
  190. }