StatusBar.cs 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. //
  2. // StatusBar.cs: a statusbar for an application
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. // TODO:
  8. // Add mouse support
  9. using System;
  10. using NStack;
  11. namespace Terminal.Gui {
  12. /// <summary>
  13. /// <see cref="StatusItem"/> objects are contained by <see cref="StatusBar"/> <see cref="View"/>s.
  14. /// Each <see cref="StatusItem"/> has a title, a shortcut (hotkey), and an <see cref="Action"/> that will be invoked when the
  15. /// <see cref="StatusItem.Shortcut"/> is pressed.
  16. /// The <see cref="StatusItem.Shortcut"/> will be a global hotkey for the application in the current context of the screen.
  17. /// The colour of the <see cref="StatusItem.Title"/> will be changed after each ~.
  18. /// A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
  19. /// *Help* as <see cref="ColorScheme.HotNormal"/>.
  20. /// </summary>
  21. public class StatusItem {
  22. /// <summary>
  23. /// Initializes a new <see cref="StatusItem"/>.
  24. /// </summary>
  25. /// <param name="shortcut">Shortcut to activate the <see cref="StatusItem"/>.</param>
  26. /// <param name="title">Title for the <see cref="StatusItem"/>.</param>
  27. /// <param name="action">Action to invoke when the <see cref="StatusItem"/> is activated.</param>
  28. public StatusItem (Key shortcut, ustring title, Action action)
  29. {
  30. Title = title ?? "";
  31. Shortcut = shortcut;
  32. Action = action;
  33. }
  34. /// <summary>
  35. /// Gets the global shortcut to invoke the action on the menu.
  36. /// </summary>
  37. public Key Shortcut { get; }
  38. /// <summary>
  39. /// Gets or sets the title.
  40. /// </summary>
  41. /// <value>The title.</value>
  42. /// <remarks>
  43. /// The colour of the <see cref="StatusItem.Title"/> will be changed after each ~.
  44. /// A <see cref="StatusItem.Title"/> set to `~F1~ Help` will render as *F1* using <see cref="ColorScheme.HotNormal"/> and
  45. /// *Help* as <see cref="ColorScheme.HotNormal"/>.
  46. /// </remarks>
  47. public ustring Title { get; set; }
  48. /// <summary>
  49. /// Gets or sets the action to be invoked when the statusbar item is triggered
  50. /// </summary>
  51. /// <value>Action to invoke.</value>
  52. public Action Action { get; }
  53. };
  54. /// <summary>
  55. /// A status bar is a <see cref="View"/> that snaps to the bottom of a <see cref="Toplevel"/> displaying set of <see cref="StatusItem"/>s.
  56. /// The <see cref="StatusBar"/> should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will
  57. /// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help.
  58. /// So for each context must be a new instance of a statusbar.
  59. /// </summary>
  60. public class StatusBar : View {
  61. // After attempting to implement this, I noticed that there are hard dependencies
  62. // on StatusBar and MenuBars within core. They will need to be refactored for having the
  63. // StatusBar work at the top
  64. #if SNAP_TO_TOP
  65. /// <summary>
  66. /// The style supported by StatusBar
  67. /// </summary>
  68. public enum StatusBarStyle {
  69. Default = 0,
  70. /// <summary>
  71. /// The StatusBar will snap at the the bottom line of the Parent view.
  72. /// If the console window is made larger while the app is runing, the StatusBar
  73. /// will continue to snap to the bottom line of the Parent, staying visible.
  74. /// On consoles that support resizing of console apps (e.g. Windows Terminal and ConEmu),
  75. /// if the console window is subsequently made shorter, the status bar will remain visible
  76. /// as the Parent view resizes. If Parent is null, the StatusBar will snap to the bottom line
  77. /// of the console window.
  78. /// This is the default.
  79. /// </summary>
  80. SnapToBottom = Default,
  81. /// <summary>
  82. /// The StatusBar will act identically to MenuBar, snapping to the first line of the
  83. /// console window.
  84. /// </summary>
  85. SnapToTop = 1,
  86. }
  87. public StatusBarStyle Style { get; set; } = StatusBarStyle.Default;
  88. #endif
  89. /// <summary>
  90. /// The parent view of the <see cref="StatusBar"/>.
  91. /// </summary>
  92. public View Parent { get; set; }
  93. /// <summary>
  94. /// The items that compose the <see cref="StatusBar"/>
  95. /// </summary>
  96. public StatusItem [] Items { get; set; }
  97. /// <summary>
  98. /// Initializes a new instance of the <see cref="StatusBar"/> class.
  99. /// </summary>
  100. public StatusBar () : this (items: new StatusItem [] { }) { }
  101. /// <summary>
  102. /// Initializes a new instance of the <see cref="StatusBar"/> class with the specified set of <see cref="StatusItem"/>s.
  103. /// The <see cref="StatusBar"/> will be drawn on the lowest line of the terminal or <see cref="StatusBar.Parent"/> (if not null).
  104. /// </summary>
  105. /// <param name="items">A list of statusbar items.</param>
  106. public StatusBar (StatusItem [] items) : base ()
  107. {
  108. Width = Dim.Fill ();
  109. Height = 1;
  110. Items = items;
  111. CanFocus = false;
  112. ColorScheme = Colors.Menu;
  113. X = 0;
  114. Y = Driver.Rows - 1;
  115. Width = Dim.Fill ();
  116. Height = 1;
  117. LayoutComplete += (e) => {
  118. X = 0;
  119. Height = 1;
  120. #if SNAP_TO_TOP
  121. switch (Style) {
  122. case StatusBarStyle.SnapToTop:
  123. X = 0;
  124. Y = 0;
  125. break;
  126. case StatusBarStyle.SnapToBottom:
  127. #endif
  128. if (Parent == null) {
  129. Y = Driver.Rows - 1;
  130. } else {
  131. Y = Pos.Bottom (Parent);
  132. }
  133. #if SNAP_TO_TOP
  134. break;
  135. }
  136. #endif
  137. };
  138. }
  139. Attribute ToggleScheme (Attribute scheme)
  140. {
  141. var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
  142. Driver.SetAttribute (result);
  143. return result;
  144. }
  145. ///<inheritdoc/>
  146. public override void Redraw (Rect bounds)
  147. {
  148. //if (Frame.Y != Driver.Rows - 1) {
  149. // Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height);
  150. // Y = Driver.Rows - 1;
  151. // SetNeedsDisplay ();
  152. //}
  153. Move (0, 0);
  154. Driver.SetAttribute (ColorScheme.Normal);
  155. for (int i = 0; i < Frame.Width; i++)
  156. Driver.AddRune (' ');
  157. Move (1, 0);
  158. var scheme = ColorScheme.Normal;
  159. Driver.SetAttribute (scheme);
  160. for (int i = 0; i < Items.Length; i++) {
  161. var title = Items [i].Title;
  162. for (int n = 0; n < title.Length; n++) {
  163. if (title [n] == '~') {
  164. scheme = ToggleScheme (scheme);
  165. continue;
  166. }
  167. Driver.AddRune (title [n]);
  168. }
  169. Driver.AddRune (' ');
  170. }
  171. }
  172. ///<inheritdoc/>
  173. public override bool ProcessHotKey (KeyEvent kb)
  174. {
  175. foreach (var item in Items) {
  176. if (kb.Key == item.Shortcut) {
  177. item.Action?.Invoke ();
  178. return true;
  179. }
  180. }
  181. return false;
  182. }
  183. ///<inheritdoc/>
  184. public override bool MouseEvent (MouseEvent me)
  185. {
  186. if (me.Flags != MouseFlags.Button1Clicked)
  187. return false;
  188. int pos = 1;
  189. for (int i = 0; i < Items.Length; i++) {
  190. if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) {
  191. Run (Items [i].Action);
  192. }
  193. pos += GetItemTitleLength (Items [i].Title) + 1;
  194. }
  195. return true;
  196. }
  197. int GetItemTitleLength (ustring title)
  198. {
  199. int len = 0;
  200. foreach (var ch in title) {
  201. if (ch == '~')
  202. continue;
  203. len++;
  204. }
  205. return len;
  206. }
  207. void Run (Action action)
  208. {
  209. if (action == null)
  210. return;
  211. Application.MainLoop.AddIdle (() => {
  212. action ();
  213. return false;
  214. });
  215. }
  216. }
  217. }