Core.cs 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452
  1. //
  2. // Core.cs: The core engine for gui.cs
  3. //
  4. // Authors:
  5. // Miguel de Icaza ([email protected])
  6. //
  7. // Pending:
  8. // - Check for NeedDisplay on the hierarchy and repaint
  9. // - Layout support
  10. // - "Colors" type or "Attributes" type?
  11. // - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors?
  12. //
  13. // Optimziations
  14. // - Add rendering limitation to the exposed area
  15. using System;
  16. using System.Collections;
  17. using System.Collections.Generic;
  18. using System.Threading;
  19. using System.Linq;
  20. namespace Terminal {
  21. public class Responder {
  22. public virtual bool CanFocus { get; set; }
  23. public virtual bool HasFocus { get; internal set; }
  24. // Key handling
  25. /// <summary>
  26. /// This method can be overwritten by view that
  27. /// want to provide accelerator functionality
  28. /// (Alt-key for example).
  29. /// </summary>
  30. /// <remarks>
  31. /// <para>
  32. /// Before keys are sent to the subview on the
  33. /// current view, all the views are
  34. /// processed and the key is passed to the widgets
  35. /// to allow some of them to process the keystroke
  36. /// as a hot-key. </para>
  37. /// <para>
  38. /// For example, if you implement a button that
  39. /// has a hotkey ok "o", you would catch the
  40. /// combination Alt-o here. If the event is
  41. /// caught, you must return true to stop the
  42. /// keystroke from being dispatched to other
  43. /// views.
  44. /// </para>
  45. /// </remarks>
  46. public virtual bool ProcessHotKey (KeyEvent kb)
  47. {
  48. return false;
  49. }
  50. /// <summary>
  51. /// If the view is focused, gives the view a
  52. /// chance to process the keystroke.
  53. /// </summary>
  54. /// <remarks>
  55. /// <para>
  56. /// Views can override this method if they are
  57. /// interested in processing the given keystroke.
  58. /// If they consume the keystroke, they must
  59. /// return true to stop the keystroke from being
  60. /// processed by other widgets or consumed by the
  61. /// widget engine. If they return false, the
  62. /// keystroke will be passed using the ProcessColdKey
  63. /// method to other views to process.
  64. /// </para>
  65. /// <para>
  66. /// The View implementation does nothing but return false,
  67. /// so it is not necessary to call base.ProcessKey if you
  68. /// derive directly from View, but you should if you derive
  69. /// other View subclasses.
  70. /// </para>
  71. /// </remarks>
  72. public virtual bool ProcessKey (KeyEvent kb)
  73. {
  74. return false;
  75. }
  76. /// <summary>
  77. /// This method can be overwritten by views that
  78. /// want to provide accelerator functionality
  79. /// (Alt-key for example), but without
  80. /// interefering with normal ProcessKey behavior.
  81. /// </summary>
  82. /// <remarks>
  83. /// <para>
  84. /// After keys are sent to the subviews on the
  85. /// current view, all the view are
  86. /// processed and the key is passed to the views
  87. /// to allow some of them to process the keystroke
  88. /// as a cold-key. </para>
  89. /// <para>
  90. /// This functionality is used, for example, by
  91. /// default buttons to act on the enter key.
  92. /// Processing this as a hot-key would prevent
  93. /// non-default buttons from consuming the enter
  94. /// keypress when they have the focus.
  95. /// </para>
  96. /// </remarks>
  97. public virtual bool ProcessColdKey (KeyEvent kb)
  98. {
  99. return false;
  100. }
  101. // Mouse events
  102. public virtual bool MouseEvent (MouseEvent me)
  103. {
  104. return false;
  105. }
  106. }
  107. /// <summary>
  108. /// View is the base class for all views on the screen and represents a visible element that can render itself and contains zero or more nested views.
  109. /// </summary>
  110. /// <remarks>
  111. /// <para>
  112. /// The View defines the base functionality for user interface elements in Terminal/gui.cs. Views
  113. /// can contain one or more subviews, can respond to user input and render themselves on the screen.
  114. /// </para>
  115. /// <para>
  116. /// Views are created with a specified rectangle region (the frame) that is relative to the container
  117. /// that they are added into.
  118. /// </para>
  119. /// <para>
  120. /// Subviews can be added to a View by calling the Add method. The container of a view is the
  121. /// Superview.
  122. /// </para>
  123. /// <para>
  124. /// Developers can call the SetNeedsDisplay method on the view to flag a region or the entire view
  125. /// as requiring to be redrawn.
  126. /// </para>
  127. /// <para>
  128. /// Views have a ColorScheme property that defines the default colors that subviews
  129. /// should use for rendering. This ensures that the views fit in the context where
  130. /// they are being used, and allows for themes to be plugged in. For example, the
  131. /// default colors for windows and toplevels uses a blue background, while it uses
  132. /// a white background for dialog boxes and a red background for errors.
  133. /// </para>
  134. /// <para>
  135. /// If a ColorScheme is not set on a view, the result of the ColorScheme is the
  136. /// value of the SuperView and the value might only be valid once a view has been
  137. /// added to a SuperView, so your subclasses should not rely on ColorScheme being
  138. /// set at construction time.
  139. /// </para>
  140. /// <para>
  141. /// Using ColorSchemes has the advantage that your application will work both
  142. /// in color as well as black and white displays.
  143. /// </para>
  144. /// </remarks>
  145. public class View : Responder, IEnumerable {
  146. View container = null;
  147. View focused = null;
  148. /// <summary>
  149. /// Points to the current driver in use by the view, it is a convenience property
  150. /// for simplifying the development of new views.
  151. /// </summary>
  152. public static ConsoleDriver Driver = Application.Driver;
  153. static IList<View> empty = new List<View> (0).AsReadOnly ();
  154. List<View> subviews;
  155. /// <summary>
  156. /// This returns a list of the subviews contained by this view.
  157. /// </summary>
  158. /// <value>The subviews.</value>
  159. public IList<View> Subviews => subviews == null ? empty : subviews.AsReadOnly ();
  160. internal Rect NeedDisplay { get; private set; } = Rect.Empty;
  161. // The frame for the object
  162. Rect frame;
  163. /// <summary>
  164. /// Gets or sets an identifier for the view;
  165. /// </summary>
  166. /// <value>The identifier.</value>
  167. public string Id { get; set; } = "";
  168. /// <summary>
  169. /// Gets or sets a value indicating whether this <see cref="T:Terminal.View"/> want mouse position reports.
  170. /// </summary>
  171. /// <value><c>true</c> if want mouse position reports; otherwise, <c>false</c>.</value>
  172. public virtual bool WantMousePositionReports { get; set; } = false;
  173. /// <summary>
  174. /// Gets or sets the frame for the view.
  175. /// </summary>
  176. /// <value>The frame.</value>
  177. /// <remarks>
  178. /// Altering the Frame of a view will trigger the redrawing of the
  179. /// view as well as the redrawing of the affected regions in the superview.
  180. /// </remarks>
  181. public Rect Frame {
  182. get => frame;
  183. set {
  184. if (SuperView != null) {
  185. SuperView.SetNeedsDisplay (frame);
  186. SuperView.SetNeedsDisplay (value);
  187. }
  188. frame = value;
  189. SetNeedsDisplay (frame);
  190. }
  191. }
  192. /// <summary>
  193. /// Gets an enumerator that enumerates the subviews in this view.
  194. /// </summary>
  195. /// <returns>The enumerator.</returns>
  196. public IEnumerator GetEnumerator ()
  197. {
  198. foreach (var v in subviews)
  199. yield return v;
  200. }
  201. /// <summary>
  202. /// The bounds represent the View-relative rectangle used for this view. Updates to the Bounds update the Frame, and has the same side effects as updating the frame.
  203. /// </summary>
  204. /// <value>The bounds.</value>
  205. public Rect Bounds {
  206. get => new Rect (Point.Empty, Frame.Size);
  207. set {
  208. Frame = new Rect (frame.Location, value.Size);
  209. }
  210. }
  211. /// <summary>
  212. /// Returns the container for this view, or null if this view has not been added to a container.
  213. /// </summary>
  214. /// <value>The super view.</value>
  215. public View SuperView => container;
  216. /// <summary>
  217. /// Initializes a new instance of the <see cref="T:Terminal.View"/> class with the specified frame. This is the default constructor.
  218. /// </summary>
  219. /// <param name="frame">The region covered by this view.</param>
  220. public View (Rect frame)
  221. {
  222. this.Frame = frame;
  223. CanFocus = false;
  224. }
  225. /// <summary>
  226. /// Invoke to flag that this view needs to be redisplayed, by any code
  227. /// that alters the state of the view.
  228. /// </summary>
  229. public void SetNeedsDisplay ()
  230. {
  231. SetNeedsDisplay (Frame);
  232. }
  233. /// <summary>
  234. /// Flags the specified rectangle region on this view as needing to be repainted.
  235. /// </summary>
  236. /// <param name="region">The region that must be flagged for repaint.</param>
  237. public void SetNeedsDisplay (Rect region)
  238. {
  239. if (NeedDisplay.IsEmpty)
  240. NeedDisplay = region;
  241. else {
  242. var x = Math.Min (NeedDisplay.X, region.X);
  243. var y = Math.Min (NeedDisplay.Y, region.Y);
  244. var w = Math.Max (NeedDisplay.Width, region.Width);
  245. var h = Math.Max (NeedDisplay.Height, region.Height);
  246. NeedDisplay = new Rect (x, y, w, h);
  247. }
  248. if (container != null)
  249. container.ChildNeedsDisplay ();
  250. if (subviews == null)
  251. return;
  252. foreach (var view in subviews)
  253. if (view.Frame.IntersectsWith (region)) {
  254. view.SetNeedsDisplay (Rect.Intersect (view.Frame, region));
  255. }
  256. }
  257. internal bool childNeedsDisplay;
  258. /// <summary>
  259. /// Flags this view for requiring the children views to be repainted.
  260. /// </summary>
  261. public void ChildNeedsDisplay ()
  262. {
  263. childNeedsDisplay = true;
  264. if (container != null)
  265. container.ChildNeedsDisplay ();
  266. }
  267. /// <summary>
  268. /// Adds a subview to this view.
  269. /// </summary>
  270. /// <remarks>
  271. /// </remarks>
  272. public virtual void Add (View view)
  273. {
  274. if (view == null)
  275. return;
  276. if (subviews == null)
  277. subviews = new List<View> ();
  278. subviews.Add (view);
  279. view.container = this;
  280. if (view.CanFocus)
  281. CanFocus = true;
  282. SetNeedsDisplay ();
  283. }
  284. /// <summary>
  285. /// Adds the specified views to the view.
  286. /// </summary>
  287. /// <param name="views">Array of one or more views (can be optional parameter).</param>
  288. public void Add (params View [] views)
  289. {
  290. if (views == null)
  291. return;
  292. foreach (var view in views)
  293. Add (view);
  294. }
  295. /// <summary>
  296. /// Removes all the widgets from this container.
  297. /// </summary>
  298. /// <remarks>
  299. /// </remarks>
  300. public virtual void RemoveAll ()
  301. {
  302. if (subviews == null)
  303. return;
  304. while (subviews.Count > 0) {
  305. var view = subviews [0];
  306. Remove (view);
  307. subviews.RemoveAt (0);
  308. }
  309. }
  310. /// <summary>
  311. /// Removes a widget from this container.
  312. /// </summary>
  313. /// <remarks>
  314. /// </remarks>
  315. public virtual void Remove (View view)
  316. {
  317. if (view == null)
  318. return;
  319. SetNeedsDisplay ();
  320. var touched = view.Frame;
  321. subviews.Remove (view);
  322. view.container = null;
  323. if (subviews.Count < 1)
  324. this.CanFocus = false;
  325. foreach (var v in subviews) {
  326. if (v.Frame.IntersectsWith (touched))
  327. view.SetNeedsDisplay ();
  328. }
  329. }
  330. /// <summary>
  331. /// Clears the view region with the current color.
  332. /// </summary>
  333. /// <remarks>
  334. /// <para>
  335. /// This clears the entire region used by this view.
  336. /// </para>
  337. /// </remarks>
  338. public void Clear ()
  339. {
  340. var h = Frame.Height;
  341. var w = Frame.Width;
  342. for (int line = 0; line < h; line++) {
  343. Move (0, line);
  344. for (int col = 0; col < w; col++)
  345. Driver.AddCh (' ');
  346. }
  347. }
  348. /// <summary>
  349. /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
  350. /// </summary>
  351. /// <param name="col">View-based column.</param>
  352. /// <param name="row">View-based row.</param>
  353. /// <param name="rcol">Absolute column, display relative.</param>
  354. /// <param name="rrow">Absolute row, display relative.</param>
  355. /// <param name="clipped">Whether to clip the result of the ViewToScreen method, if set to true, the rcol, rrow values are clamped to the screen dimensions.</param>
  356. internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  357. {
  358. // Computes the real row, col relative to the screen.
  359. rrow = row + frame.Y;
  360. rcol = col + frame.X;
  361. var ccontainer = container;
  362. while (ccontainer != null) {
  363. rrow += ccontainer.frame.Y;
  364. rcol += ccontainer.frame.X;
  365. ccontainer = ccontainer.container;
  366. }
  367. // The following ensures that the cursor is always in the screen boundaries.
  368. if (clipped) {
  369. rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
  370. rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
  371. }
  372. }
  373. /// <summary>
  374. /// Converts a point from screen coordinates into the view coordinate space.
  375. /// </summary>
  376. /// <returns>The mapped point.</returns>
  377. /// <param name="x">X screen-coordinate point.</param>
  378. /// <param name="y">Y screen-coordinate point.</param>
  379. public Point ScreenToView (int x, int y)
  380. {
  381. if (SuperView == null) {
  382. return new Point (x - Frame.X, y - frame.Y);
  383. } else {
  384. var parent = SuperView.ScreenToView (x, y);
  385. return new Point (parent.X - frame.X, parent.Y - frame.Y);
  386. }
  387. }
  388. // Converts a rectangle in view coordinates to screen coordinates.
  389. Rect RectToScreen (Rect rect)
  390. {
  391. ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
  392. return new Rect (x, y, rect.Width, rect.Height);
  393. }
  394. // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
  395. Rect ScreenClip (Rect rect)
  396. {
  397. var x = rect.X < 0 ? 0 : rect.X;
  398. var y = rect.Y < 0 ? 0 : rect.Y;
  399. var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
  400. var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
  401. return new Rect (x, y, w, h);
  402. }
  403. /// <summary>
  404. /// Draws a frame in the current view, clipped by the boundary of this view
  405. /// </summary>
  406. /// <param name="rect">Rectangular region for the frame to be drawn.</param>
  407. /// <param name="fill">If set to <c>true</c> it fill will the contents.</param>
  408. public void DrawFrame (Rect rect, bool fill = false)
  409. {
  410. var scrRect = RectToScreen (rect);
  411. var savedClip = Driver.Clip;
  412. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  413. Driver.DrawFrame (scrRect, fill);
  414. Driver.Clip = savedClip;
  415. }
  416. /// <summary>
  417. /// Utility function to draw strings that contain a hotkey
  418. /// </summary>
  419. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  420. /// <param name="hotColor">Hot color.</param>
  421. /// <param name="normalColor">Normal color.</param>
  422. public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
  423. {
  424. Driver.SetAttribute (normalColor);
  425. foreach (var c in text) {
  426. if (c == '_') {
  427. Driver.SetAttribute (hotColor);
  428. continue;
  429. }
  430. Driver.AddCh (c);
  431. Driver.SetAttribute (normalColor);
  432. }
  433. }
  434. /// <summary>
  435. /// Utility function to draw strings that contains a hotkey using a colorscheme and the "focused" state.
  436. /// </summary>
  437. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  438. /// <param name="focused">If set to <c>true</c> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
  439. /// <param name="scheme">The color scheme to use.</param>
  440. public void DrawHotString (string text, bool focused, ColorScheme scheme)
  441. {
  442. if (focused)
  443. DrawHotString (text, scheme.HotFocus, scheme.Focus);
  444. else
  445. DrawHotString (text, scheme.HotNormal, scheme.Normal);
  446. }
  447. /// <summary>
  448. /// This moves the cursor to the specified column and row in the view.
  449. /// </summary>
  450. /// <returns>The move.</returns>
  451. /// <param name="col">Col.</param>
  452. /// <param name="row">Row.</param>
  453. public void Move (int col, int row)
  454. {
  455. ViewToScreen (col, row, out var rcol, out var rrow);
  456. Driver.Move (rcol, rrow);
  457. }
  458. /// <summary>
  459. /// Positions the cursor in the right position based on the currently focused view in the chain.
  460. /// </summary>
  461. public virtual void PositionCursor ()
  462. {
  463. if (focused != null)
  464. focused.PositionCursor ();
  465. else
  466. Move (frame.X, frame.Y);
  467. }
  468. /// <summary>
  469. /// Gets or sets a value indicating whether this <see cref="T:Terminal.View"/> has focus.
  470. /// </summary>
  471. /// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
  472. public override bool HasFocus {
  473. get {
  474. return base.HasFocus;
  475. }
  476. internal set {
  477. if (base.HasFocus != value)
  478. SetNeedsDisplay ();
  479. base.HasFocus = value;
  480. }
  481. }
  482. /// <summary>
  483. /// Returns the currently focused view inside this view, or null if nothing is focused.
  484. /// </summary>
  485. /// <value>The focused.</value>
  486. public View Focused => focused;
  487. /// <summary>
  488. /// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
  489. /// </summary>
  490. /// <value>The most focused.</value>
  491. public View MostFocused {
  492. get {
  493. if (Focused == null)
  494. return null;
  495. var most = Focused.MostFocused;
  496. if (most != null)
  497. return most;
  498. return Focused;
  499. }
  500. }
  501. /// <summary>
  502. /// The color scheme for this view, if it is not defined, it returns the parent's
  503. /// color scheme.
  504. /// </summary>
  505. public ColorScheme ColorScheme {
  506. get {
  507. if (colorScheme == null)
  508. return SuperView?.ColorScheme;
  509. return colorScheme;
  510. }
  511. set {
  512. colorScheme = value;
  513. }
  514. }
  515. ColorScheme colorScheme;
  516. /// <summary>
  517. /// Displays the specified character in the specified column and row.
  518. /// </summary>
  519. /// <param name="col">Col.</param>
  520. /// <param name="row">Row.</param>
  521. /// <param name="ch">Ch.</param>
  522. public void AddCh (int col, int row, int ch)
  523. {
  524. if (row < 0 || col < 0)
  525. return;
  526. if (row > frame.Height - 1 || col > frame.Width - 1)
  527. return;
  528. Move (col, row);
  529. Driver.AddCh (ch);
  530. }
  531. /// <summary>
  532. /// Removes the SetNeedsDisplay and the ChildNeedsDisplay setting on this view.
  533. /// </summary>
  534. protected void ClearNeedsDisplay ()
  535. {
  536. NeedDisplay = Rect.Empty;
  537. childNeedsDisplay = false;
  538. }
  539. /// <summary>
  540. /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
  541. /// </summary>
  542. /// <remarks>
  543. /// The region argument is relative to the view itself.
  544. /// </remarks>
  545. public virtual void Redraw (Rect region)
  546. {
  547. var clipRect = new Rect (Point.Empty, frame.Size);
  548. if (subviews != null) {
  549. foreach (var view in subviews) {
  550. if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
  551. if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
  552. // TODO: optimize this by computing the intersection of region and view.Bounds
  553. view.Redraw (view.Bounds);
  554. }
  555. view.NeedDisplay = Rect.Empty;
  556. view.childNeedsDisplay = false;
  557. }
  558. }
  559. }
  560. ClearNeedsDisplay ();
  561. }
  562. /// <summary>
  563. /// Focuses the specified sub-view.
  564. /// </summary>
  565. /// <param name="view">View.</param>
  566. public void SetFocus (View view)
  567. {
  568. if (view == null)
  569. return;
  570. //Console.WriteLine ($"Request to focus {view}");
  571. if (!view.CanFocus)
  572. return;
  573. if (focused == view)
  574. return;
  575. // Make sure that this view is a subview
  576. View c;
  577. for (c = view.container; c != null; c = c.container)
  578. if (c == this)
  579. break;
  580. if (c == null)
  581. throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
  582. if (focused != null)
  583. focused.HasFocus = false;
  584. focused = view;
  585. focused.HasFocus = true;
  586. focused.EnsureFocus ();
  587. }
  588. public override bool ProcessKey (KeyEvent kb)
  589. {
  590. if (Focused?.ProcessKey (kb) == true)
  591. return true;
  592. return false;
  593. }
  594. public override bool ProcessHotKey (KeyEvent kb)
  595. {
  596. if (subviews == null || subviews.Count == 0)
  597. return false;
  598. foreach (var view in subviews)
  599. if (view.ProcessHotKey (kb))
  600. return true;
  601. return false;
  602. }
  603. public override bool ProcessColdKey (KeyEvent kb)
  604. {
  605. if (subviews == null || subviews.Count == 0)
  606. return false;
  607. foreach (var view in subviews)
  608. if (view.ProcessColdKey (kb))
  609. return true;
  610. return false;
  611. }
  612. /// <summary>
  613. /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
  614. /// </summary>
  615. public void EnsureFocus ()
  616. {
  617. if (focused == null)
  618. FocusFirst ();
  619. }
  620. /// <summary>
  621. /// Focuses the first focusable subview if one exists.
  622. /// </summary>
  623. public void FocusFirst ()
  624. {
  625. if (subviews == null) {
  626. SuperView.SetFocus (this);
  627. return;
  628. }
  629. foreach (var view in subviews) {
  630. if (view.CanFocus) {
  631. SetFocus (view);
  632. return;
  633. }
  634. }
  635. }
  636. /// <summary>
  637. /// Focuses the last focusable subview if one exists.
  638. /// </summary>
  639. public void FocusLast ()
  640. {
  641. if (subviews == null)
  642. return;
  643. for (int i = subviews.Count; i > 0;) {
  644. i--;
  645. View v = subviews [i];
  646. if (v.CanFocus) {
  647. SetFocus (v);
  648. return;
  649. }
  650. }
  651. }
  652. /// <summary>
  653. /// Focuses the previous view.
  654. /// </summary>
  655. /// <returns><c>true</c>, if previous was focused, <c>false</c> otherwise.</returns>
  656. public bool FocusPrev ()
  657. {
  658. if (subviews == null || subviews.Count == 0)
  659. return false;
  660. if (focused == null) {
  661. FocusLast ();
  662. return true;
  663. }
  664. int focused_idx = -1;
  665. for (int i = subviews.Count; i > 0;) {
  666. i--;
  667. View w = subviews [i];
  668. if (w.HasFocus) {
  669. if (w.FocusPrev ())
  670. return true;
  671. focused_idx = i;
  672. continue;
  673. }
  674. if (w.CanFocus && focused_idx != -1) {
  675. focused.HasFocus = false;
  676. if (w.CanFocus)
  677. w.FocusLast ();
  678. SetFocus (w);
  679. return true;
  680. }
  681. }
  682. if (focused_idx != -1) {
  683. FocusLast ();
  684. return true;
  685. }
  686. if (focused != null) {
  687. focused.HasFocus = false;
  688. focused = null;
  689. }
  690. return false;
  691. }
  692. /// <summary>
  693. /// Focuses the next view.
  694. /// </summary>
  695. /// <returns><c>true</c>, if next was focused, <c>false</c> otherwise.</returns>
  696. public bool FocusNext ()
  697. {
  698. if (subviews == null || subviews.Count == 0)
  699. return false;
  700. if (focused == null) {
  701. FocusFirst ();
  702. return focused != null;
  703. }
  704. int n = subviews.Count;
  705. int focused_idx = -1;
  706. for (int i = 0; i < n; i++) {
  707. View w = subviews [i];
  708. if (w.HasFocus) {
  709. if (w.FocusNext ())
  710. return true;
  711. focused_idx = i;
  712. continue;
  713. }
  714. if (w.CanFocus && focused_idx != -1) {
  715. focused.HasFocus = false;
  716. if (w != null && w.CanFocus)
  717. w.FocusFirst ();
  718. SetFocus (w);
  719. return true;
  720. }
  721. }
  722. if (focused != null) {
  723. focused.HasFocus = false;
  724. focused = null;
  725. }
  726. return false;
  727. }
  728. /// <summary>
  729. /// This virtual method is invoked when a view starts executing or
  730. /// when the dimensions of the view have changed, for example in
  731. /// response to the container view or terminal resizing.
  732. /// </summary>
  733. public virtual void LayoutSubviews ()
  734. {
  735. }
  736. /// <summary>
  737. /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.View"/>.
  738. /// </summary>
  739. /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.View"/>.</returns>
  740. public override string ToString ()
  741. {
  742. return $"{GetType ().Name}({Id})({Frame})";
  743. }
  744. }
  745. /// <summary>
  746. /// Toplevel views can be modally executed.
  747. /// </summary>
  748. /// <remarks>
  749. /// <para>
  750. /// Toplevels can be modally executing views, and they return control
  751. /// to the caller when the "Running" property is set to false.
  752. /// </para>
  753. /// </remarks>
  754. public class Toplevel : View {
  755. /// <summary>
  756. /// This flag is checked on each iteration of the mainloop and it continues
  757. /// running until this flag is set to false.
  758. /// </summary>
  759. public bool Running;
  760. /// <summary>
  761. /// Initializes a new instance of the <see cref="T:Terminal.Toplevel"/> class.
  762. /// </summary>
  763. /// <param name="frame">Frame.</param>
  764. public Toplevel (Rect frame) : base (frame)
  765. {
  766. ColorScheme = Colors.Base;
  767. }
  768. /// <summary>
  769. /// Convenience factory method that creates a new toplevel with the current terminal dimensions.
  770. /// </summary>
  771. /// <returns>The create.</returns>
  772. public static Toplevel Create ()
  773. {
  774. return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
  775. }
  776. public override bool CanFocus {
  777. get => true;
  778. }
  779. public override bool ProcessKey (KeyEvent kb)
  780. {
  781. if (base.ProcessKey (kb))
  782. return true;
  783. switch (kb.Key) {
  784. case Key.ControlC:
  785. // TODO: stop current execution of this container
  786. break;
  787. case Key.ControlZ:
  788. Driver.Suspend ();
  789. return true;
  790. #if false
  791. case Key.F5:
  792. Application.DebugDrawBounds = !Application.DebugDrawBounds;
  793. SetNeedsDisplay ();
  794. return true;
  795. #endif
  796. case Key.Tab:
  797. var old = Focused;
  798. if (!FocusNext ())
  799. FocusNext ();
  800. if (old != Focused) {
  801. old?.SetNeedsDisplay ();
  802. Focused?.SetNeedsDisplay ();
  803. }
  804. return true;
  805. case Key.BackTab:
  806. old = Focused;
  807. if (!FocusPrev ())
  808. FocusPrev ();
  809. if (old != Focused) {
  810. old?.SetNeedsDisplay ();
  811. Focused?.SetNeedsDisplay ();
  812. }
  813. return true;
  814. case Key.ControlL:
  815. Application.Refresh ();
  816. return true;
  817. }
  818. return false;
  819. }
  820. }
  821. /// <summary>
  822. /// A toplevel view that draws a frame around its region and has a "ContentView" subview where the contents are added.
  823. /// </summary>
  824. public class Window : Toplevel, IEnumerable {
  825. View contentView;
  826. string title;
  827. /// <summary>
  828. /// The title to be displayed for this window.
  829. /// </summary>
  830. /// <value>The title.</value>
  831. public string Title {
  832. get => title;
  833. set {
  834. title = value;
  835. SetNeedsDisplay ();
  836. }
  837. }
  838. class ContentView : View {
  839. public ContentView (Rect frame) : base (frame) { }
  840. }
  841. /// <summary>
  842. /// Initializes a new instance of the <see cref="T:Terminal.Window"/> class with an optioanl title
  843. /// </summary>
  844. /// <param name="frame">Frame.</param>
  845. /// <param name="title">Title.</param>
  846. public Window (Rect frame, string title = null) : base (frame)
  847. {
  848. this.Title = title;
  849. var cFrame = new Rect (1, 1, frame.Width - 2, frame.Height - 2);
  850. contentView = new ContentView (cFrame);
  851. base.Add (contentView);
  852. }
  853. /// <summary>
  854. /// Enumerates the various views in the ContentView.
  855. /// </summary>
  856. /// <returns>The enumerator.</returns>
  857. public new IEnumerator GetEnumerator ()
  858. {
  859. return contentView.GetEnumerator ();
  860. }
  861. void DrawFrame ()
  862. {
  863. DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), true);
  864. }
  865. /// <summary>
  866. /// Add the specified view to the ContentView.
  867. /// </summary>
  868. /// <param name="view">View to add to the window.</param>
  869. public override void Add (View view)
  870. {
  871. contentView.Add (view);
  872. }
  873. public override void Redraw (Rect bounds)
  874. {
  875. if (!NeedDisplay.IsEmpty) {
  876. Driver.SetAttribute (ColorScheme.Normal);
  877. DrawFrame ();
  878. if (HasFocus)
  879. Driver.SetAttribute (ColorScheme.Normal);
  880. var width = Frame.Width;
  881. if (Title != null && width > 4) {
  882. Move (1, 0);
  883. Driver.AddCh (' ');
  884. var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
  885. Driver.AddStr (str);
  886. Driver.AddCh (' ');
  887. }
  888. Driver.SetAttribute (ColorScheme.Normal);
  889. }
  890. contentView.Redraw (contentView.Bounds);
  891. ClearNeedsDisplay ();
  892. }
  893. #if false
  894. //
  895. // It does not look like the event is raised on clicked-drag
  896. // need to figure that out.
  897. //
  898. Point? dragPosition;
  899. public override bool MouseEvent(MouseEvent me)
  900. {
  901. if (me.Flags == MouseFlags.Button1Pressed){
  902. if (dragPosition.HasValue) {
  903. var dx = me.X - dragPosition.Value.X;
  904. var dy = me.Y - dragPosition.Value.Y;
  905. var nx = Frame.X + dx;
  906. var ny = Frame.Y + dy;
  907. if (nx < 0)
  908. nx = 0;
  909. if (ny < 0)
  910. ny = 0;
  911. Demo.ml2.Text = $"{dx},{dy}";
  912. dragPosition = new Point (me.X, me.Y);
  913. // TODO: optimize, only SetNeedsDisplay on the before/after regions.
  914. if (SuperView == null)
  915. Application.Refresh ();
  916. else
  917. SuperView.SetNeedsDisplay ();
  918. Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
  919. SetNeedsDisplay ();
  920. return true;
  921. } else {
  922. dragPosition = new Point (me.X, me.Y);
  923. Application.GrabMouse (this);
  924. Demo.ml2.Text = $"Starting at {dragPosition}";
  925. return true;
  926. }
  927. }
  928. if (me.Flags == MouseFlags.Button1Released) {
  929. Application.UngrabMouse ();
  930. dragPosition = null;
  931. //Driver.StopReportingMouseMoves ();
  932. }
  933. Demo.ml.Text = me.ToString ();
  934. return false;
  935. }
  936. #endif
  937. }
  938. /// <summary>
  939. /// The application driver for gui.cs
  940. /// </summary>
  941. /// <remarks>
  942. /// <para>
  943. /// You can hook up to the Iteration event to have your method
  944. /// invoked on each iteration of the mainloop.
  945. /// </para>
  946. /// <para>
  947. /// Creates a mainloop to process input events, handle timers and
  948. /// other sources of data. It is accessible via the MainLoop property.
  949. /// </para>
  950. /// <para>
  951. /// When invoked sets the SynchronizationContext to one that is tied
  952. /// to the mainloop, allowing user code to use async/await.
  953. /// </para>
  954. /// </remarks>
  955. public class Application {
  956. /// <summary>
  957. /// The current Console Driver in use.
  958. /// </summary>
  959. public static ConsoleDriver Driver = new CursesDriver ();
  960. /// <summary>
  961. /// The Toplevel object used for the application on startup.
  962. /// </summary>
  963. /// <value>The top.</value>
  964. public static Toplevel Top { get; private set; }
  965. /// <summary>
  966. /// The current toplevel object. This is updated when Application.Run enters and leaves and points to the current toplevel.
  967. /// </summary>
  968. /// <value>The current.</value>
  969. public static Toplevel Current { get; private set; }
  970. /// <summary>
  971. /// The mainloop driver for the applicaiton
  972. /// </summary>
  973. /// <value>The main loop.</value>
  974. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  975. static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
  976. /// <summary>
  977. /// This event is raised on each iteration of the
  978. /// main loop.
  979. /// </summary>
  980. /// <remarks>
  981. /// See also <see cref="Timeout"/>
  982. /// </remarks>
  983. static public event EventHandler Iteration;
  984. /// <summary>
  985. /// Returns a rectangle that is centered in the screen for the provided size.
  986. /// </summary>
  987. /// <returns>The centered rect.</returns>
  988. /// <param name="size">Size for the rectangle.</param>
  989. public static Rect MakeCenteredRect (Size size)
  990. {
  991. return new Rect (new Point ((Driver.Cols - size.Width) / 2, (Driver.Rows - size.Height) / 2), size);
  992. }
  993. //
  994. // provides the sync context set while executing code in gui.cs, to let
  995. // users use async/await on their code
  996. //
  997. class MainLoopSyncContext : SynchronizationContext {
  998. Mono.Terminal.MainLoop mainLoop;
  999. public MainLoopSyncContext (Mono.Terminal.MainLoop mainLoop)
  1000. {
  1001. this.mainLoop = mainLoop;
  1002. }
  1003. public override SynchronizationContext CreateCopy ()
  1004. {
  1005. return new MainLoopSyncContext (MainLoop);
  1006. }
  1007. public override void Post (SendOrPostCallback d, object state)
  1008. {
  1009. mainLoop.AddIdle (() => {
  1010. d (state);
  1011. return false;
  1012. });
  1013. }
  1014. public override void Send (SendOrPostCallback d, object state)
  1015. {
  1016. mainLoop.Invoke (() => {
  1017. d (state);
  1018. });
  1019. }
  1020. }
  1021. /// <summary>
  1022. /// Initializes the Application
  1023. /// </summary>
  1024. public static void Init ()
  1025. {
  1026. if (Top != null)
  1027. return;
  1028. Driver.Init (TerminalResized);
  1029. MainLoop = new Mono.Terminal.MainLoop ();
  1030. SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
  1031. Top = Toplevel.Create ();
  1032. Current = Top;
  1033. }
  1034. /// <summary>
  1035. /// Captures the execution state for the provided TopLevel view.
  1036. /// </summary>
  1037. public class RunState : IDisposable {
  1038. internal RunState (Toplevel view)
  1039. {
  1040. Toplevel = view;
  1041. }
  1042. internal Toplevel Toplevel;
  1043. /// <summary>
  1044. /// Releases all resource used by the <see cref="T:Terminal.Application.RunState"/> object.
  1045. /// </summary>
  1046. /// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="T:Terminal.Application.RunState"/>. The
  1047. /// <see cref="Dispose"/> method leaves the <see cref="T:Terminal.Application.RunState"/> in an unusable state. After
  1048. /// calling <see cref="Dispose"/>, you must release all references to the
  1049. /// <see cref="T:Terminal.Application.RunState"/> so the garbage collector can reclaim the memory that the
  1050. /// <see cref="T:Terminal.Application.RunState"/> was occupying.</remarks>
  1051. public void Dispose ()
  1052. {
  1053. Dispose (true);
  1054. GC.SuppressFinalize (this);
  1055. }
  1056. /// <summary>
  1057. /// Dispose the specified disposing.
  1058. /// </summary>
  1059. /// <returns>The dispose.</returns>
  1060. /// <param name="disposing">If set to <c>true</c> disposing.</param>
  1061. public virtual void Dispose (bool disposing)
  1062. {
  1063. if (Toplevel != null) {
  1064. Application.End (Toplevel);
  1065. Toplevel = null;
  1066. }
  1067. }
  1068. }
  1069. static void ProcessKeyEvent (KeyEvent ke)
  1070. {
  1071. if (Current.ProcessHotKey (ke))
  1072. return;
  1073. if (Current.ProcessKey (ke))
  1074. return;
  1075. // Process the key normally
  1076. if (Current.ProcessColdKey (ke))
  1077. return;
  1078. }
  1079. static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
  1080. {
  1081. var startFrame = start.Frame;
  1082. if (!startFrame.Contains (x, y)) {
  1083. resx = 0;
  1084. resy = 0;
  1085. return null;
  1086. }
  1087. if (start.Subviews != null){
  1088. int count = start.Subviews.Count;
  1089. if (count > 0) {
  1090. var rx = x - startFrame.X;
  1091. var ry = y - startFrame.Y;
  1092. for (int i = count - 1; i >= 0; i--) {
  1093. View v = start.Subviews [i];
  1094. if (v.Frame.Contains (rx, ry)) {
  1095. var deep = FindDeepestView (v, rx, ry, out resx, out resy);
  1096. if (deep == null)
  1097. return v;
  1098. return deep;
  1099. }
  1100. }
  1101. }
  1102. }
  1103. resx = x-startFrame.X;
  1104. resy = y-startFrame.Y;
  1105. return start;
  1106. }
  1107. static View mouseGrabView;
  1108. /// <summary>
  1109. /// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called.
  1110. /// </summary>
  1111. /// <returns>The grab.</returns>
  1112. /// <param name="view">View that will receive all mouse events until UngrabMouse is invoked.</param>
  1113. public static void GrabMouse (View view)
  1114. {
  1115. if (view == null)
  1116. return;
  1117. mouseGrabView = view;
  1118. }
  1119. /// <summary>
  1120. /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
  1121. /// </summary>
  1122. public static void UngrabMouse ()
  1123. {
  1124. mouseGrabView = null;
  1125. }
  1126. /// <summary>
  1127. /// Merely a debugging aid to see the raw mouse events
  1128. /// </summary>
  1129. static public Action<MouseEvent> RootMouseEvent;
  1130. static void ProcessMouseEvent (MouseEvent me)
  1131. {
  1132. RootMouseEvent?.Invoke (me);
  1133. if (mouseGrabView != null) {
  1134. var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
  1135. var nme = new MouseEvent () {
  1136. X = newxy.X,
  1137. Y = newxy.Y,
  1138. Flags = me.Flags
  1139. };
  1140. mouseGrabView.MouseEvent (me);
  1141. return;
  1142. }
  1143. int rx, ry;
  1144. var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
  1145. if (view != null) {
  1146. if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition)
  1147. return;
  1148. var nme = new MouseEvent () {
  1149. X = rx,
  1150. Y = ry,
  1151. Flags = me.Flags
  1152. };
  1153. // Should we bubbled up the event, if it is not handled?
  1154. view.MouseEvent (nme);
  1155. }
  1156. }
  1157. static public RunState Begin (Toplevel toplevel)
  1158. {
  1159. if (toplevel == null)
  1160. throw new ArgumentNullException (nameof (toplevel));
  1161. var rs = new RunState (toplevel);
  1162. Init ();
  1163. toplevels.Push (toplevel);
  1164. Current = toplevel;
  1165. Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
  1166. toplevel.LayoutSubviews ();
  1167. toplevel.FocusFirst ();
  1168. Redraw (toplevel);
  1169. toplevel.PositionCursor ();
  1170. Driver.Refresh ();
  1171. return rs;
  1172. }
  1173. static public void End (RunState rs)
  1174. {
  1175. if (rs == null)
  1176. throw new ArgumentNullException (nameof (rs));
  1177. rs.Dispose ();
  1178. }
  1179. static void Shutdown ()
  1180. {
  1181. Driver.End ();
  1182. }
  1183. static void Redraw (View view)
  1184. {
  1185. view.Redraw (view.Bounds);
  1186. Driver.Refresh ();
  1187. }
  1188. static void Refresh (View view)
  1189. {
  1190. view.Redraw (view.Bounds);
  1191. Driver.Refresh ();
  1192. }
  1193. /// <summary>
  1194. /// Triggers a refresh of the entire display.
  1195. /// </summary>
  1196. public static void Refresh ()
  1197. {
  1198. Driver.RedrawTop ();
  1199. View last = null;
  1200. foreach (var v in toplevels.Reverse ()) {
  1201. v.SetNeedsDisplay ();
  1202. v.Redraw (v.Bounds);
  1203. last = v;
  1204. }
  1205. last?.PositionCursor ();
  1206. Driver.Refresh ();
  1207. }
  1208. internal static void End (View view)
  1209. {
  1210. if (toplevels.Peek () != view)
  1211. throw new ArgumentException ("The view that you end with must be balanced");
  1212. toplevels.Pop ();
  1213. if (toplevels.Count == 0)
  1214. Shutdown ();
  1215. else {
  1216. Current = toplevels.Peek () as Toplevel;
  1217. Refresh ();
  1218. }
  1219. }
  1220. /// <summary>
  1221. /// Runs the main loop for the created dialog
  1222. /// </summary>
  1223. /// <remarks>
  1224. /// Use the wait parameter to control whether this is a
  1225. /// blocking or non-blocking call.
  1226. /// </remarks>
  1227. public static void RunLoop (RunState state, bool wait = true)
  1228. {
  1229. if (state == null)
  1230. throw new ArgumentNullException (nameof (state));
  1231. if (state.Toplevel == null)
  1232. throw new ObjectDisposedException ("state");
  1233. for (state.Toplevel.Running = true; state.Toplevel.Running;) {
  1234. if (MainLoop.EventsPending (wait)) {
  1235. MainLoop.MainIteration ();
  1236. if (Iteration != null)
  1237. Iteration (null, EventArgs.Empty);
  1238. } else if (wait == false)
  1239. return;
  1240. if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
  1241. state.Toplevel.Redraw (state.Toplevel.Bounds);
  1242. if (DebugDrawBounds)
  1243. DrawBounds (state.Toplevel);
  1244. state.Toplevel.PositionCursor ();
  1245. Driver.Refresh ();
  1246. }
  1247. }
  1248. }
  1249. internal static bool DebugDrawBounds;
  1250. // Need to look into why this does not work properly.
  1251. static void DrawBounds (View v)
  1252. {
  1253. v.DrawFrame (v.Frame, false);
  1254. if (v.Subviews != null && v.Subviews.Count > 0)
  1255. foreach (var sub in v.Subviews)
  1256. DrawBounds (sub);
  1257. }
  1258. /// <summary>
  1259. /// Runs the application with the built-in toplevel view
  1260. /// </summary>
  1261. public static void Run ()
  1262. {
  1263. Run (Top);
  1264. }
  1265. /// <summary>
  1266. /// Runs the main loop on the given container.
  1267. /// </summary>
  1268. /// <remarks>
  1269. /// <para>
  1270. /// This method is used to start processing events
  1271. /// for the main application, but it is also used to
  1272. /// run modal dialog boxes.
  1273. /// </para>
  1274. /// <para>
  1275. /// To make a toplevel stop execution, set the "Running"
  1276. /// property to false.
  1277. /// </para>
  1278. /// </remarks>
  1279. public static void Run (Toplevel view)
  1280. {
  1281. var runToken = Begin (view);
  1282. RunLoop (runToken);
  1283. End (runToken);
  1284. }
  1285. /// <summary>
  1286. /// Stops running the most recent toplevel
  1287. /// </summary>
  1288. public static void RequestStop ()
  1289. {
  1290. var ct = Current as Toplevel;
  1291. Current.Running = false;
  1292. }
  1293. static void TerminalResized ()
  1294. {
  1295. foreach (var t in toplevels) {
  1296. t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
  1297. }
  1298. }
  1299. }
  1300. }