Toplevel.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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)"/>.
  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)"/>.
  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 event 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 OnKeyDown (KeyEvent keyEvent)
  114. {
  115. if (base.OnKeyDown (keyEvent)) {
  116. return true;
  117. }
  118. switch (keyEvent.Key) {
  119. case Key.AltMask:
  120. if (MenuBar != null && MenuBar.OnKeyDown (keyEvent)) {
  121. return true;
  122. }
  123. break;
  124. }
  125. return false;
  126. }
  127. ///<inheritdoc/>
  128. public override bool OnKeyUp (KeyEvent keyEvent)
  129. {
  130. if (base.OnKeyUp (keyEvent)) {
  131. return true;
  132. }
  133. switch (keyEvent.Key) {
  134. case Key.AltMask:
  135. if (MenuBar != null && MenuBar.OnKeyUp (keyEvent)) {
  136. return true;
  137. }
  138. break;
  139. }
  140. return false;
  141. }
  142. ///<inheritdoc/>
  143. public override bool ProcessKey (KeyEvent keyEvent)
  144. {
  145. if (base.ProcessKey (keyEvent))
  146. return true;
  147. switch (keyEvent.Key) {
  148. case Key.Q | Key.CtrlMask:
  149. // FIXED: stop current execution of this container
  150. Application.RequestStop ();
  151. break;
  152. case Key.Z | Key.CtrlMask:
  153. Driver.Suspend ();
  154. return true;
  155. #if false
  156. case Key.F5:
  157. Application.DebugDrawBounds = !Application.DebugDrawBounds;
  158. SetNeedsDisplay ();
  159. return true;
  160. #endif
  161. case Key.Tab:
  162. case Key.CursorRight:
  163. case Key.CursorDown:
  164. case Key.I | Key.CtrlMask: // Unix
  165. var old = GetDeepestFocusedSubview (Focused);
  166. if (!FocusNext ())
  167. FocusNext ();
  168. if (old != Focused) {
  169. old?.SetNeedsDisplay ();
  170. Focused?.SetNeedsDisplay ();
  171. } else {
  172. FocusNearestView (GetToplevelSubviews (true));
  173. }
  174. return true;
  175. case Key.CursorLeft:
  176. case Key.CursorUp:
  177. case Key.BackTab:
  178. old = GetDeepestFocusedSubview (Focused);
  179. if (!FocusPrev ())
  180. FocusPrev ();
  181. if (old != Focused) {
  182. old?.SetNeedsDisplay ();
  183. Focused?.SetNeedsDisplay ();
  184. } else {
  185. FocusNearestView (GetToplevelSubviews (false));
  186. }
  187. return true;
  188. case Key.L | Key.CtrlMask:
  189. Application.Refresh ();
  190. return true;
  191. }
  192. return false;
  193. }
  194. View GetDeepestFocusedSubview (View view)
  195. {
  196. if (view == null) {
  197. return null;
  198. }
  199. foreach (var v in view.Subviews) {
  200. if (v.HasFocus) {
  201. return GetDeepestFocusedSubview (v);
  202. }
  203. }
  204. return view;
  205. }
  206. IEnumerable<View> GetToplevelSubviews (bool isForward)
  207. {
  208. if (SuperView == null) {
  209. return null;
  210. }
  211. HashSet<View> views = new HashSet<View> ();
  212. foreach (var v in SuperView.Subviews) {
  213. views.Add (v);
  214. }
  215. return isForward ? views : views.Reverse ();
  216. }
  217. void FocusNearestView (IEnumerable<View> views)
  218. {
  219. if (views == null) {
  220. return;
  221. }
  222. bool found = false;
  223. foreach (var v in views) {
  224. if (v == this) {
  225. found = true;
  226. }
  227. if (found && v != this) {
  228. v.EnsureFocus ();
  229. if (SuperView.Focused != null && SuperView.Focused != this) {
  230. return;
  231. }
  232. }
  233. }
  234. }
  235. ///<inheritdoc/>
  236. public override void Add (View view)
  237. {
  238. if (this == Application.Top) {
  239. AddMenuStatusBar (view);
  240. }
  241. base.Add (view);
  242. }
  243. internal void AddMenuStatusBar (View view)
  244. {
  245. if (view is MenuBar) {
  246. MenuBar = view as MenuBar;
  247. }
  248. if (view is StatusBar) {
  249. StatusBar = view as StatusBar;
  250. }
  251. }
  252. ///<inheritdoc/>
  253. public override void Remove (View view)
  254. {
  255. if (this is Toplevel toplevel && toplevel.MenuBar != null) {
  256. RemoveMenuStatusBar (view);
  257. }
  258. base.Remove (view);
  259. }
  260. ///<inheritdoc/>
  261. public override void RemoveAll ()
  262. {
  263. if (this == Application.Top) {
  264. MenuBar?.Dispose ();
  265. MenuBar = null;
  266. StatusBar?.Dispose ();
  267. StatusBar = null;
  268. }
  269. base.RemoveAll ();
  270. }
  271. internal void RemoveMenuStatusBar (View view)
  272. {
  273. if (view is MenuBar) {
  274. MenuBar?.Dispose ();
  275. MenuBar = null;
  276. }
  277. if (view is StatusBar) {
  278. StatusBar?.Dispose ();
  279. StatusBar = null;
  280. }
  281. }
  282. internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
  283. {
  284. nx = Math.Max (x, 0);
  285. nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx;
  286. bool m, s;
  287. if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
  288. m = Application.Top.MenuBar != null;
  289. } else {
  290. m = ((Toplevel)SuperView).MenuBar != null;
  291. }
  292. int l;
  293. if (SuperView == null || SuperView is Toplevel) {
  294. l = m ? 1 : 0;
  295. } else {
  296. l = 0;
  297. }
  298. ny = Math.Max (y, l);
  299. if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) {
  300. s = Application.Top.StatusBar != null && Application.Top.StatusBar.Visible;
  301. } else {
  302. s = ((Toplevel)SuperView).StatusBar != null && ((Toplevel)SuperView).StatusBar.Visible;
  303. }
  304. if (SuperView == null || SuperView is Toplevel) {
  305. l = s ? Driver.Rows - 1 : Driver.Rows;
  306. } else {
  307. l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height;
  308. }
  309. ny = Math.Min (ny, l);
  310. ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
  311. }
  312. internal void PositionToplevels ()
  313. {
  314. PositionToplevel (this);
  315. foreach (var top in Subviews) {
  316. if (top is Toplevel) {
  317. PositionToplevel ((Toplevel)top);
  318. }
  319. }
  320. }
  321. private void PositionToplevel (Toplevel top)
  322. {
  323. EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
  324. if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
  325. if (top.X is Pos.PosAbsolute && top.Bounds.X != nx) {
  326. top.X = nx;
  327. }
  328. if (top.Y is Pos.PosAbsolute && top.Bounds.Y != ny) {
  329. top.Y = ny;
  330. }
  331. }
  332. if (top.StatusBar != null) {
  333. if (ny + top.Frame.Height > top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
  334. if (top.Height is Dim.DimFill)
  335. top.Height = Dim.Fill () - (top.StatusBar.Visible ? 1 : 0);
  336. }
  337. if (top.StatusBar.Frame.Y != top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) {
  338. top.StatusBar.Y = top.Frame.Height - (top.StatusBar.Visible ? 1 : 0);
  339. top.LayoutSubviews ();
  340. }
  341. top.BringSubviewToFront (top.StatusBar);
  342. }
  343. }
  344. ///<inheritdoc/>
  345. public override void Redraw (Rect bounds)
  346. {
  347. Application.CurrentView = this;
  348. if (IsCurrentTop || this == Application.Top) {
  349. if (!NeedDisplay.IsEmpty) {
  350. Driver.SetAttribute (Colors.TopLevel.Normal);
  351. // This is the Application.Top. Clear just the region we're being asked to redraw
  352. // (the bounds passed to us).
  353. Clear (bounds);
  354. Driver.SetAttribute (Colors.Base.Normal);
  355. PositionToplevels ();
  356. }
  357. foreach (var view in Subviews) {
  358. if (view.Frame.IntersectsWith (bounds)) {
  359. view.SetNeedsLayout ();
  360. view.SetNeedsDisplay (view.Bounds);
  361. }
  362. }
  363. ClearNeedsDisplay ();
  364. }
  365. base.Redraw (base.Bounds);
  366. }
  367. /// <summary>
  368. /// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel)"/> after
  369. /// the views have been laid out, and before the views are drawn for the first time.
  370. /// </summary>
  371. public virtual void WillPresent ()
  372. {
  373. FocusFirst ();
  374. }
  375. }
  376. }