Toplevel.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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()"/>.
  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 event EventHandler 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 (this, EventArgs.Empty);
  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. HashSet<View> views = new HashSet<View> ();
  167. foreach (var v in SuperView.Subviews) {
  168. views.Add (v);
  169. }
  170. return isForward ? views : views.Reverse ();
  171. }
  172. void FocusNearestView (IEnumerable<View> views)
  173. {
  174. bool found = false;
  175. foreach (var v in views) {
  176. if (v == this) {
  177. found = true;
  178. }
  179. if (found && v != this) {
  180. v.EnsureFocus ();
  181. if (SuperView.Focused != null && SuperView.Focused != this) {
  182. return;
  183. }
  184. }
  185. }
  186. }
  187. ///<inheritdoc cref="Add"/>
  188. public override void Add (View view)
  189. {
  190. if (this == Application.Top) {
  191. if (view is MenuBar)
  192. MenuBar = view as MenuBar;
  193. if (view is StatusBar)
  194. StatusBar = view as StatusBar;
  195. }
  196. base.Add (view);
  197. }
  198. ///<inheritdoc/>
  199. public override void Remove (View view)
  200. {
  201. if (this == Application.Top) {
  202. if (view is MenuBar)
  203. MenuBar = null;
  204. if (view is StatusBar)
  205. StatusBar = null;
  206. }
  207. base.Remove (view);
  208. }
  209. ///<inheritdoc/>
  210. public override void RemoveAll ()
  211. {
  212. if (this == Application.Top) {
  213. MenuBar = null;
  214. StatusBar = null;
  215. }
  216. base.RemoveAll ();
  217. }
  218. internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny)
  219. {
  220. nx = Math.Max (x, 0);
  221. nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx;
  222. bool m, s;
  223. if (SuperView == null || SuperView.GetType () != typeof (Toplevel))
  224. m = Application.Top.MenuBar != null;
  225. else
  226. m = ((Toplevel)SuperView).MenuBar != null;
  227. int l = m ? 1 : 0;
  228. ny = Math.Max (y, l);
  229. if (SuperView == null || SuperView.GetType () != typeof (Toplevel))
  230. s = Application.Top.StatusBar != null;
  231. else
  232. s = ((Toplevel)SuperView).StatusBar != null;
  233. l = s ? Driver.Rows - 1 : Driver.Rows;
  234. ny = Math.Min (ny, l);
  235. ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny;
  236. }
  237. internal void PositionToplevels ()
  238. {
  239. if (this != Application.Top) {
  240. EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny);
  241. if ((nx != Frame.X || ny != Frame.Y) && LayoutStyle != LayoutStyle.Computed) {
  242. X = nx;
  243. Y = ny;
  244. }
  245. } else {
  246. foreach (var top in Subviews) {
  247. if (top is Toplevel) {
  248. EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
  249. if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
  250. top.X = nx;
  251. top.Y = ny;
  252. }
  253. if (StatusBar != null) {
  254. if (ny + top.Frame.Height > Driver.Rows - 1) {
  255. if (top.Height is Dim.DimFill)
  256. top.Height = Dim.Fill () - 1;
  257. }
  258. if (StatusBar.Frame.Y != Driver.Rows - 1) {
  259. StatusBar.Y = Driver.Rows - 1;
  260. SetNeedsDisplay ();
  261. }
  262. }
  263. }
  264. }
  265. }
  266. }
  267. ///<inheritdoc/>
  268. public override void Redraw (Rect bounds)
  269. {
  270. Application.CurrentView = this;
  271. if (IsCurrentTop || this == Application.Top) {
  272. if (NeedDisplay != null && !NeedDisplay.IsEmpty) {
  273. Driver.SetAttribute (Colors.TopLevel.Normal);
  274. // This is the Application.Top. Clear just the region we're being asked to redraw
  275. // (the bounds passed to us).
  276. Clear (bounds);
  277. Driver.SetAttribute (Colors.Base.Normal);
  278. }
  279. foreach (var view in Subviews) {
  280. if (view.Frame.IntersectsWith (bounds)) {
  281. view.SetNeedsLayout ();
  282. view.SetNeedsDisplay (view.Bounds);
  283. }
  284. }
  285. ClearNeedsDisplay ();
  286. }
  287. base.Redraw (base.Bounds);
  288. }
  289. /// <summary>
  290. /// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel, bool)"/> after
  291. /// the views have been laid out, and before the views are drawn for the first time.
  292. /// </summary>
  293. public virtual void WillPresent ()
  294. {
  295. FocusFirst ();
  296. }
  297. }
  298. }