Toplevel.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. //
  2. // Toplevel.cs: Toplevel views can be modally executed
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. using System;
  8. using System.Collections.Generic;
  9. using System.ComponentModel;
  10. using System.Linq;
  11. namespace Terminal.Gui {
  12. /// <summary>
  13. /// Toplevel views can be modally executed.
  14. /// </summary>
  15. /// <remarks>
  16. /// <para>
  17. /// Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, bool)"/>.
  18. /// They return control to the caller when <see cref="Application.RequestStop()"/> has
  19. /// been called (which sets the <see cref="Toplevel.Running"/> property to false).
  20. /// </para>
  21. /// <para>
  22. /// A Toplevel is created when an application initialzies Terminal.Gui by callling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
  23. /// The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created
  24. /// and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and
  25. /// call <see cref="Application.Run(Toplevel, bool)"/>.
  26. /// </para>
  27. /// <para>
  28. /// Toplevels can also opt-in to more sophisticated initialization
  29. /// by implementing <see cref="ISupportInitialize"/>. When they do
  30. /// so, the <see cref="ISupportInitialize.BeginInit"/> and
  31. /// <see cref="ISupportInitialize.EndInit"/> methods will be called
  32. /// before running the view.
  33. /// If first-run-only initialization is preferred, the <see cref="ISupportInitializeNotification"/>
  34. /// can be implemented too, in which case the <see cref="ISupportInitialize"/>
  35. /// methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
  36. /// is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
  37. /// to override base class layout code optimally by doing so only on first run,
  38. /// instead of on every run.
  39. /// </para>
  40. /// </remarks>
  41. public class Toplevel : View {
  42. /// <summary>
  43. /// Gets or sets whether the <see cref="MainLoop"/> for this <see cref="Toplevel"/> is running or not.
  44. /// </summary>
  45. /// <remarks>
  46. /// Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead.
  47. /// </remarks>
  48. public bool Running { get; set; }
  49. /// <summary>
  50. /// Fired once the Toplevel's <see cref="MainLoop"/> has started it's first iteration.
  51. /// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
  52. /// changes. A Ready event handler is a good place to finalize initialization after calling `<see cref="Application.Run()"/>(topLevel)`.
  53. /// </summary>
  54. public Action Ready;
  55. /// <summary>
  56. /// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered it's first iteration of the loop.
  57. /// </summary>
  58. internal virtual void OnReady ()
  59. {
  60. Ready?.Invoke ();
  61. }
  62. /// <summary>
  63. /// Initializes a new instance of the <see cref="Toplevel"/> class with the specified absolute layout.
  64. /// </summary>
  65. /// <param name="frame">A superview-relative rectangle specifying the location and size for the new Toplevel</param>
  66. public Toplevel (Rect frame) : base (frame)
  67. {
  68. Initialize ();
  69. }
  70. /// <summary>
  71. /// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, defaulting to full screen.
  72. /// </summary>
  73. public Toplevel () : base ()
  74. {
  75. Initialize ();
  76. Width = Dim.Fill ();
  77. Height = Dim.Fill ();
  78. }
  79. void Initialize ()
  80. {
  81. ColorScheme = Colors.Base;
  82. }
  83. /// <summary>
  84. /// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
  85. /// </summary>
  86. /// <returns>The create.</returns>
  87. public static Toplevel Create ()
  88. {
  89. return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
  90. }
  91. /// <summary>
  92. /// Gets or sets a value indicating whether this <see cref="Toplevel"/> can focus.
  93. /// </summary>
  94. /// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
  95. public override bool CanFocus {
  96. get => true;
  97. }
  98. /// <summary>
  99. /// Determines whether the <see cref="Toplevel"/> is modal or not.
  100. /// Causes <see cref="ProcessKey(KeyEvent)"/> to propagate keys upwards
  101. /// by default unless set to <see langword="true"/>.
  102. /// </summary>
  103. public bool Modal { get; set; }
  104. /// <summary>
  105. /// Gets or sets the menu for this Toplevel
  106. /// </summary>
  107. public MenuBar MenuBar { get; set; }
  108. /// <summary>
  109. /// Gets or sets the status bar for this Toplevel
  110. /// </summary>
  111. public StatusBar StatusBar { get; set; }
  112. ///<inheritdoc/>
  113. public override bool ProcessKey (KeyEvent keyEvent)
  114. {
  115. if (base.ProcessKey (keyEvent))
  116. return true;
  117. switch (keyEvent.Key) {
  118. case Key.ControlQ:
  119. // FIXED: stop current execution of this container
  120. Application.RequestStop ();
  121. break;
  122. case Key.ControlZ:
  123. Driver.Suspend ();
  124. return true;
  125. #if false
  126. case Key.F5:
  127. Application.DebugDrawBounds = !Application.DebugDrawBounds;
  128. SetNeedsDisplay ();
  129. return true;
  130. #endif
  131. case Key.Tab:
  132. case Key.CursorRight:
  133. case Key.CursorDown:
  134. case Key.ControlI: // Unix
  135. var old = Focused;
  136. if (!FocusNext ())
  137. FocusNext ();
  138. if (old != Focused) {
  139. old?.SetNeedsDisplay ();
  140. Focused?.SetNeedsDisplay ();
  141. } else {
  142. FocusNearestView (GetToplevelSubviews (true));
  143. }
  144. return true;
  145. case Key.CursorLeft:
  146. case Key.CursorUp:
  147. case Key.BackTab:
  148. old = Focused;
  149. if (!FocusPrev ())
  150. FocusPrev ();
  151. if (old != Focused) {
  152. old?.SetNeedsDisplay ();
  153. Focused?.SetNeedsDisplay ();
  154. } else {
  155. FocusNearestView (GetToplevelSubviews (false));
  156. }
  157. return true;
  158. case Key.ControlL:
  159. Application.Refresh ();
  160. return true;
  161. }
  162. return false;
  163. }
  164. IEnumerable<View> GetToplevelSubviews (bool isForward)
  165. {
  166. if (SuperView == null) {
  167. return null;
  168. }
  169. HashSet<View> views = new HashSet<View> ();
  170. foreach (var v in SuperView.Subviews) {
  171. views.Add (v);
  172. }
  173. return isForward ? views : views.Reverse ();
  174. }
  175. void FocusNearestView (IEnumerable<View> views)
  176. {
  177. if (views == null) {
  178. return;
  179. }
  180. bool found = false;
  181. foreach (var v in views) {
  182. if (v == this) {
  183. found = true;
  184. }
  185. if (found && v != this) {
  186. v.EnsureFocus ();
  187. if (SuperView.Focused != null && SuperView.Focused != this) {
  188. return;
  189. }
  190. }
  191. }
  192. }
  193. ///<inheritdoc/>
  194. public override void Add (View view)
  195. {
  196. if (this == Application.Top) {
  197. if (view is MenuBar)
  198. MenuBar = view as MenuBar;
  199. if (view is StatusBar)
  200. StatusBar = view as StatusBar;
  201. }
  202. base.Add (view);
  203. }
  204. ///<inheritdoc/>
  205. public override void Remove (View view)
  206. {
  207. if (this is Toplevel && ((Toplevel)this).MenuBar != null) {
  208. if (view is MenuBar) {
  209. MenuBar?.Dispose ();
  210. MenuBar = null;
  211. }
  212. if (view is StatusBar) {
  213. StatusBar?.Dispose ();
  214. StatusBar = null;
  215. }
  216. }
  217. base.Remove (view);
  218. }
  219. ///<inheritdoc/>
  220. public override void RemoveAll ()
  221. {
  222. if (this == Application.Top) {
  223. MenuBar?.Dispose ();
  224. MenuBar = null;
  225. StatusBar?.Dispose ();
  226. StatusBar = null;
  227. }
  228. base.RemoveAll ();
  229. }
  230. internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
  231. {
  232. nx = Math.Max (x, 0);
  233. nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx;
  234. bool m, s;
  235. if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
  236. m = Application.Top.MenuBar != null;
  237. } else {
  238. m = ((Toplevel)SuperView).MenuBar != null;
  239. }
  240. int l;
  241. if (SuperView == null || SuperView is Toplevel) {
  242. l = m ? 1 : 0;
  243. } else {
  244. l = 0;
  245. }
  246. ny = Math.Max (y, l);
  247. if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
  248. s = Application.Top.StatusBar != null;
  249. } else {
  250. s = ((Toplevel)SuperView).StatusBar != null;
  251. }
  252. if (SuperView == null || SuperView is Toplevel) {
  253. l = s ? Driver.Rows - 1 : Driver.Rows;
  254. } else {
  255. l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height;
  256. }
  257. ny = Math.Min (ny, l);
  258. ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
  259. }
  260. internal void PositionToplevels ()
  261. {
  262. PositionToplevel (this);
  263. foreach (var top in Subviews) {
  264. if (top is Toplevel) {
  265. PositionToplevel ((Toplevel)top);
  266. }
  267. }
  268. }
  269. private void PositionToplevel (Toplevel top)
  270. {
  271. EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
  272. if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
  273. if (top.X is Pos.PosAbsolute && top.Bounds.X != nx) {
  274. top.X = nx;
  275. }
  276. if (top.Y is Pos.PosAbsolute && top.Bounds.Y != ny) {
  277. top.Y = ny;
  278. }
  279. }
  280. if (StatusBar != null) {
  281. if (ny + top.Frame.Height > top.Frame.Height - 1) {
  282. if (top.Height is Dim.DimFill)
  283. top.Height = Dim.Fill () - 1;
  284. }
  285. if (StatusBar.Frame.Y != Frame.Height - 1) {
  286. StatusBar.Y = Frame.Height - 1;
  287. SetNeedsDisplay ();
  288. }
  289. }
  290. }
  291. ///<inheritdoc/>
  292. public override void Redraw (Rect bounds)
  293. {
  294. Application.CurrentView = this;
  295. if (IsCurrentTop || this == Application.Top) {
  296. if (NeedDisplay != null && !NeedDisplay.IsEmpty) {
  297. Driver.SetAttribute (Colors.TopLevel.Normal);
  298. // This is the Application.Top. Clear just the region we're being asked to redraw
  299. // (the bounds passed to us).
  300. Clear (bounds);
  301. Driver.SetAttribute (Colors.Base.Normal);
  302. PositionToplevels ();
  303. }
  304. foreach (var view in Subviews) {
  305. if (view.Frame.IntersectsWith (bounds)) {
  306. view.SetNeedsLayout ();
  307. view.SetNeedsDisplay (view.Bounds);
  308. }
  309. }
  310. ClearNeedsDisplay ();
  311. }
  312. base.Redraw (base.Bounds);
  313. }
  314. /// <summary>
  315. /// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel, bool)"/> after
  316. /// the views have been laid out, and before the views are drawn for the first time.
  317. /// </summary>
  318. public virtual void WillPresent ()
  319. {
  320. FocusFirst ();
  321. }
  322. }
  323. }