ExpanderButton.cs 6.8 KB

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