ExpanderButton.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #nullable enable
  2. using System.Text;
  3. namespace UICatalog.Scenarios;
  4. /// <summary>
  5. /// A Button that can expand or collapse a view.
  6. /// </summary>
  7. /// <remarks>
  8. /// <para>
  9. /// Add this button to a view's Border to allow the user to expand or collapse the view via either the keyboard
  10. /// (F4) or mouse.
  11. /// </para>
  12. /// <para>
  13. /// If <see cref="Orientation"/> is set to <see cref="Orientation.Vertical"/>, the button will appear
  14. /// at the top/right.
  15. /// If <see cref="Orientation"/> is set to <see cref="Orientation.Horizontal"/>, the button will
  16. /// appear at the
  17. /// bottom/left.
  18. /// </para>
  19. /// </remarks>
  20. /// <example>
  21. /// private void MyView_Initialized (object sender, EventArgs e)
  22. /// {
  23. /// Border.Add(new ExpanderButton ());
  24. /// ...
  25. /// </example>
  26. public class ExpanderButton : Button
  27. {
  28. public ExpanderButton ()
  29. {
  30. CanFocus = false;
  31. Width = 1;
  32. Height = 1;
  33. NoDecorations = true;
  34. NoPadding = true;
  35. AddCommand (Command.HotKey, Toggle);
  36. AddCommand (Command.Toggle, Toggle);
  37. KeyBindings.Add (Key.F4, Command.Toggle);
  38. Orientation = Orientation.Vertical;
  39. HighlightStates = MouseState.In;
  40. Initialized += ExpanderButton_Initialized;
  41. EnabledChanged += (_, _) => { ShowHide (); };
  42. }
  43. private void ShowHide ()
  44. {
  45. if (!Enabled)
  46. {
  47. Visible = false;
  48. }
  49. if (SuperView is Border { } border)
  50. {
  51. switch (Orientation)
  52. {
  53. case Orientation.Vertical:
  54. Visible = border.Thickness.Top > 0;
  55. break;
  56. case Orientation.Horizontal:
  57. Visible = border.Border?.Thickness.Left > 0;
  58. break;
  59. }
  60. }
  61. }
  62. private void ExpanderButton_Initialized (object? sender, EventArgs e)
  63. {
  64. ShadowStyle = ShadowStyle.None;
  65. ExpandOrCollapse (Collapsed);
  66. if (SuperView is Border { } border)
  67. {
  68. border.ThicknessChanged += (_, _) => ShowHide ();
  69. }
  70. }
  71. private Orientation _orientation = Orientation.Horizontal;
  72. /// <summary>Orientation.</summary>
  73. /// <remarks>
  74. /// <para>
  75. /// If <see cref="Orientation"/> is set to <see cref="Orientation.Vertical"/>, the button will appear at the
  76. /// top/right.
  77. /// If <see cref="Orientation"/> is set to <see cref="Orientation.Horizontal"/>, the button will appear at the
  78. /// bottom/left.
  79. /// </para>
  80. /// </remarks>
  81. public Orientation Orientation
  82. {
  83. get => _orientation;
  84. set => OnOrientationChanging (value);
  85. }
  86. /// <summary>Called when the orientation is changing. Invokes the <see cref="OrientationChanging"/> event.</summary>
  87. /// <param name="newOrientation"></param>
  88. /// <returns>True of the event was cancelled.</returns>
  89. protected virtual bool OnOrientationChanging (Orientation newOrientation)
  90. {
  91. CancelEventArgs<Orientation> args = new (in _orientation, ref newOrientation);
  92. OrientationChanging?.Invoke (this, args);
  93. if (!args.Cancel)
  94. {
  95. _orientation = newOrientation;
  96. if (Orientation == Orientation.Vertical)
  97. {
  98. X = Pos.AnchorEnd () - 1;
  99. Y = 0;
  100. CollapseGlyph = new ('\u21d1'); // ⇑
  101. ExpandGlyph = new ('\u21d3'); // ⇓
  102. }
  103. else
  104. {
  105. X = 0;
  106. Y = Pos.AnchorEnd () - 1;
  107. CollapseGlyph = new ('\u21d0'); // ⇐
  108. ExpandGlyph = new ('\u21d2'); // ⇒
  109. }
  110. ExpandOrCollapse (Collapsed);
  111. }
  112. return args.Cancel;
  113. }
  114. /// <summary>
  115. /// Fired when the orientation has changed. Can be cancelled.
  116. /// </summary>
  117. public event EventHandler<CancelEventArgs<Orientation>>? OrientationChanging;
  118. /// <summary>
  119. /// The glyph that indicates the button will collapse the view.
  120. /// </summary>
  121. public Rune CollapseGlyph { get; set; }
  122. /// <summary>
  123. /// The glyph that indicates the button will expand the view.
  124. /// </summary>
  125. public Rune ExpandGlyph { get; set; }
  126. private bool _collapsed;
  127. /// <summary>
  128. /// Gets or sets a value indicating whether the view is collapsed.
  129. /// </summary>
  130. public bool Collapsed
  131. {
  132. get => _collapsed;
  133. set => OnCollapsedChanging (value);
  134. }
  135. /// <summary>Called when the orientation is changing. Invokes the <see cref="OrientationChanging"/> event.</summary>
  136. /// <param name="newValue"></param>
  137. /// <returns>True of the event was cancelled.</returns>
  138. protected virtual bool OnCollapsedChanging (bool newValue)
  139. {
  140. CancelEventArgs<bool> args = new (ref _collapsed, ref newValue);
  141. CollapsedChanging?.Invoke (this, args);
  142. if (!args.Cancel)
  143. {
  144. _collapsed = args.NewValue;
  145. ExpandOrCollapse (_collapsed);
  146. }
  147. return args.Cancel;
  148. }
  149. /// <summary>
  150. /// Fired when the orientation has changed. Can be cancelled.
  151. /// </summary>
  152. public event EventHandler<CancelEventArgs<bool>>? CollapsedChanging;
  153. /// <summary>
  154. /// Collapses or Expands the view.
  155. /// </summary>
  156. /// <returns></returns>
  157. public bool? Toggle ()
  158. {
  159. Collapsed = !Collapsed;
  160. return true;
  161. }
  162. private Dim? _previousDim;
  163. private void ExpandOrCollapse (bool collapse)
  164. {
  165. Text = $"{(Collapsed ? ExpandGlyph : CollapseGlyph)}";
  166. View? superView = SuperView;
  167. if (superView is Adornment adornment)
  168. {
  169. superView = adornment.Parent;
  170. }
  171. if (superView is null)
  172. {
  173. return;
  174. }
  175. if (collapse)
  176. {
  177. // Collapse
  178. if (Orientation == Orientation.Vertical)
  179. {
  180. _previousDim = superView.Height;
  181. superView.Height = 1;
  182. }
  183. else
  184. {
  185. _previousDim = superView.Width;
  186. superView.Width = 1;
  187. }
  188. }
  189. else
  190. {
  191. if (_previousDim is null)
  192. {
  193. return;
  194. }
  195. // Expand
  196. if (Orientation == Orientation.Vertical)
  197. {
  198. superView.Height = _previousDim;
  199. }
  200. else
  201. {
  202. superView.Width = _previousDim;
  203. }
  204. }
  205. foreach (View subview in superView.SubViews)
  206. {
  207. subview.Visible = !Collapsed;
  208. }
  209. }
  210. }