CheckBox.cs 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. #nullable enable
  2. namespace Terminal.Gui;
  3. /// <summary>
  4. /// Represents the state of a <see cref="CheckBox"/>.
  5. /// </summary>
  6. public enum CheckState
  7. {
  8. /// <summary>
  9. /// Neither checked nor unchecked.
  10. /// </summary>
  11. None,
  12. /// <summary>
  13. /// Checked.
  14. /// </summary>
  15. Checked,
  16. /// <summary>
  17. /// Not checked.
  18. /// </summary>
  19. UnChecked
  20. }
  21. /// <summary>Shows a check box that can be toggled.</summary>
  22. public class CheckBox : View
  23. {
  24. private bool _allowNone;
  25. private CheckState _checked = CheckState.UnChecked;
  26. /// <summary>
  27. /// Initializes a new instance of <see cref="CheckBox"/>.
  28. /// </summary>
  29. public CheckBox ()
  30. {
  31. Width = Dim.Auto (DimAutoStyle.Text);
  32. Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
  33. CanFocus = true;
  34. // Things this view knows how to do
  35. AddCommand (Command.Accept, OnToggle);
  36. AddCommand (Command.HotKey, OnToggle);
  37. // Default keybindings for this view
  38. KeyBindings.Add (Key.Space, Command.Accept);
  39. TitleChanged += Checkbox_TitleChanged;
  40. HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
  41. MouseClick += CheckBox_MouseClick;
  42. }
  43. private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e)
  44. {
  45. e.Handled = OnToggle () == true;
  46. }
  47. private void Checkbox_TitleChanged (object? sender, EventArgs<string> e)
  48. {
  49. base.Text = e.CurrentValue;
  50. TextFormatter.HotKeySpecifier = HotKeySpecifier;
  51. }
  52. /// <inheritdoc />
  53. public override string Text
  54. {
  55. get => base.Title;
  56. set => base.Text = base.Title = value;
  57. }
  58. /// <inheritdoc />
  59. public override Rune HotKeySpecifier
  60. {
  61. get => base.HotKeySpecifier;
  62. set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
  63. }
  64. /// <summary>
  65. /// If <see langword="true"/> allows <see cref="State"/> to be <see cref="CheckState.None"/>.
  66. /// </summary>
  67. public bool AllowCheckStateNone
  68. {
  69. get => _allowNone;
  70. set
  71. {
  72. if (_allowNone == value)
  73. {
  74. return;
  75. }
  76. _allowNone = value;
  77. if (State == CheckState.None)
  78. {
  79. State = CheckState.UnChecked;
  80. }
  81. }
  82. }
  83. /// <summary>
  84. /// The state of the <see cref="CheckBox"/>.
  85. /// </summary>
  86. /// <remarks>
  87. /// <para>
  88. /// If <see cref="AllowCheckStateNone"/> is <see langword="true"/> and <see cref="CheckState.None"/>, the <see cref="CheckBox"/>
  89. /// will display the <c>ConfigurationManager.Glyphs.CheckStateNone</c> character (☒).
  90. /// </para>
  91. /// <para>
  92. /// If <see cref="CheckState.UnChecked"/>, the <see cref="CheckBox"/>
  93. /// will display the <c>ConfigurationManager.Glyphs.CheckStateUnChecked</c> character (☐).
  94. /// </para>
  95. /// <para>
  96. /// If <see cref="CheckState.Checked"/>, the <see cref="CheckBox"/>
  97. /// will display the <c>ConfigurationManager.Glyphs.CheckStateChecked</c> character (☑).
  98. /// </para>
  99. /// </remarks>
  100. public CheckState State
  101. {
  102. get => _checked;
  103. set
  104. {
  105. if (_checked == value || (value is CheckState.None && !AllowCheckStateNone))
  106. {
  107. return;
  108. }
  109. _checked = value;
  110. UpdateTextFormatterText ();
  111. OnResizeNeeded ();
  112. }
  113. }
  114. /// <summary>Called when the <see cref="State"/> property changes. Invokes the cancelable <see cref="Toggle"/> event.</summary>
  115. /// <remarks>
  116. /// </remarks>
  117. /// <returns>If <see langword="true"/> the <see cref="Toggle"/> event was canceled.</returns>
  118. /// <remarks>
  119. /// Toggling cycles through the states <see cref="CheckState.None"/>, <see cref="CheckState.Checked"/>, and <see cref="CheckState.UnChecked"/>.
  120. /// </remarks>
  121. public bool? OnToggle ()
  122. {
  123. CheckState oldValue = State;
  124. CancelEventArgs<CheckState> e = new (ref _checked, ref oldValue);
  125. switch (State)
  126. {
  127. case CheckState.None:
  128. e.NewValue = CheckState.Checked;
  129. break;
  130. case CheckState.Checked:
  131. e.NewValue = CheckState.UnChecked;
  132. break;
  133. case CheckState.UnChecked:
  134. if (AllowCheckStateNone)
  135. {
  136. e.NewValue = CheckState.None;
  137. }
  138. else
  139. {
  140. e.NewValue = CheckState.Checked;
  141. }
  142. break;
  143. }
  144. Toggle?.Invoke (this, e);
  145. if (e.Cancel)
  146. {
  147. return e.Cancel;
  148. }
  149. // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
  150. if (OnAccept () == true)
  151. {
  152. return true;
  153. }
  154. State = e.NewValue;
  155. return true;
  156. }
  157. /// <summary>Toggle event, raised when the <see cref="CheckBox"/> is toggled.</summary>
  158. /// <remarks>
  159. /// <para>
  160. /// This event can be cancelled. If cancelled, the <see cref="CheckBox"/> will not change its state.
  161. /// </para>
  162. /// </remarks>
  163. public event EventHandler<CancelEventArgs<CheckState>>? Toggle;
  164. /// <inheritdoc/>
  165. protected override void UpdateTextFormatterText ()
  166. {
  167. base.UpdateTextFormatterText();
  168. switch (TextAlignment)
  169. {
  170. case Alignment.Start:
  171. case Alignment.Center:
  172. case Alignment.Fill:
  173. TextFormatter.Text = $"{GetCheckedGlyph ()} {Text}";
  174. break;
  175. case Alignment.End:
  176. TextFormatter.Text = $"{Text} {GetCheckedGlyph ()}";
  177. break;
  178. }
  179. }
  180. private Rune GetCheckedGlyph ()
  181. {
  182. return State switch
  183. {
  184. CheckState.Checked => Glyphs.CheckStateChecked,
  185. CheckState.UnChecked => Glyphs.CheckStateUnChecked,
  186. CheckState.None => Glyphs.CheckStateNone,
  187. _ => throw new ArgumentOutOfRangeException ()
  188. };
  189. }
  190. }