CheckBox.cs 5.8 KB

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