Toplevel.cs 8.7 KB

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