Core.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  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.Gui {
  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="padding">The padding to add to the drawn frame.</param>
  408. /// <param name="fill">If set to <c>true</c> it fill will the contents.</param>
  409. public void DrawFrame (Rect rect, int padding = 0, bool fill = false)
  410. {
  411. var scrRect = RectToScreen (rect);
  412. var savedClip = Driver.Clip;
  413. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  414. Driver.DrawFrame (scrRect, padding, fill);
  415. Driver.Clip = savedClip;
  416. }
  417. /// <summary>
  418. /// Utility function to draw strings that contain a hotkey
  419. /// </summary>
  420. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  421. /// <param name="hotColor">Hot color.</param>
  422. /// <param name="normalColor">Normal color.</param>
  423. public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
  424. {
  425. Driver.SetAttribute (normalColor);
  426. foreach (var c in text) {
  427. if (c == '_') {
  428. Driver.SetAttribute (hotColor);
  429. continue;
  430. }
  431. Driver.AddCh (c);
  432. Driver.SetAttribute (normalColor);
  433. }
  434. }
  435. /// <summary>
  436. /// Utility function to draw strings that contains a hotkey using a colorscheme and the "focused" state.
  437. /// </summary>
  438. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  439. /// <param name="focused">If set to <c>true</c> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
  440. /// <param name="scheme">The color scheme to use.</param>
  441. public void DrawHotString (string text, bool focused, ColorScheme scheme)
  442. {
  443. if (focused)
  444. DrawHotString (text, scheme.HotFocus, scheme.Focus);
  445. else
  446. DrawHotString (text, scheme.HotNormal, scheme.Normal);
  447. }
  448. /// <summary>
  449. /// This moves the cursor to the specified column and row in the view.
  450. /// </summary>
  451. /// <returns>The move.</returns>
  452. /// <param name="col">Col.</param>
  453. /// <param name="row">Row.</param>
  454. public void Move (int col, int row)
  455. {
  456. ViewToScreen (col, row, out var rcol, out var rrow);
  457. Driver.Move (rcol, rrow);
  458. }
  459. /// <summary>
  460. /// Positions the cursor in the right position based on the currently focused view in the chain.
  461. /// </summary>
  462. public virtual void PositionCursor ()
  463. {
  464. if (focused != null)
  465. focused.PositionCursor ();
  466. else
  467. Move (frame.X, frame.Y);
  468. }
  469. /// <summary>
  470. /// Gets or sets a value indicating whether this <see cref="T:Terminal.View"/> has focus.
  471. /// </summary>
  472. /// <value><c>true</c> if has focus; otherwise, <c>false</c>.</value>
  473. public override bool HasFocus {
  474. get {
  475. return base.HasFocus;
  476. }
  477. internal set {
  478. if (base.HasFocus != value)
  479. SetNeedsDisplay ();
  480. base.HasFocus = value;
  481. }
  482. }
  483. /// <summary>
  484. /// Returns the currently focused view inside this view, or null if nothing is focused.
  485. /// </summary>
  486. /// <value>The focused.</value>
  487. public View Focused => focused;
  488. /// <summary>
  489. /// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
  490. /// </summary>
  491. /// <value>The most focused.</value>
  492. public View MostFocused {
  493. get {
  494. if (Focused == null)
  495. return null;
  496. var most = Focused.MostFocused;
  497. if (most != null)
  498. return most;
  499. return Focused;
  500. }
  501. }
  502. /// <summary>
  503. /// The color scheme for this view, if it is not defined, it returns the parent's
  504. /// color scheme.
  505. /// </summary>
  506. public ColorScheme ColorScheme {
  507. get {
  508. if (colorScheme == null)
  509. return SuperView?.ColorScheme;
  510. return colorScheme;
  511. }
  512. set {
  513. colorScheme = value;
  514. }
  515. }
  516. ColorScheme colorScheme;
  517. /// <summary>
  518. /// Displays the specified character in the specified column and row.
  519. /// </summary>
  520. /// <param name="col">Col.</param>
  521. /// <param name="row">Row.</param>
  522. /// <param name="ch">Ch.</param>
  523. public void AddCh (int col, int row, int ch)
  524. {
  525. if (row < 0 || col < 0)
  526. return;
  527. if (row > frame.Height - 1 || col > frame.Width - 1)
  528. return;
  529. Move (col, row);
  530. Driver.AddCh (ch);
  531. }
  532. /// <summary>
  533. /// Removes the SetNeedsDisplay and the ChildNeedsDisplay setting on this view.
  534. /// </summary>
  535. protected void ClearNeedsDisplay ()
  536. {
  537. NeedDisplay = Rect.Empty;
  538. childNeedsDisplay = false;
  539. }
  540. /// <summary>
  541. /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
  542. /// </summary>
  543. /// <remarks>
  544. /// The region argument is relative to the view itself.
  545. /// </remarks>
  546. public virtual void Redraw (Rect region)
  547. {
  548. var clipRect = new Rect (Point.Empty, frame.Size);
  549. if (subviews != null) {
  550. foreach (var view in subviews) {
  551. if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
  552. if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
  553. // TODO: optimize this by computing the intersection of region and view.Bounds
  554. view.Redraw (view.Bounds);
  555. }
  556. view.NeedDisplay = Rect.Empty;
  557. view.childNeedsDisplay = false;
  558. }
  559. }
  560. }
  561. ClearNeedsDisplay ();
  562. }
  563. /// <summary>
  564. /// Focuses the specified sub-view.
  565. /// </summary>
  566. /// <param name="view">View.</param>
  567. public void SetFocus (View view)
  568. {
  569. if (view == null)
  570. return;
  571. //Console.WriteLine ($"Request to focus {view}");
  572. if (!view.CanFocus)
  573. return;
  574. if (focused == view)
  575. return;
  576. // Make sure that this view is a subview
  577. View c;
  578. for (c = view.container; c != null; c = c.container)
  579. if (c == this)
  580. break;
  581. if (c == null)
  582. throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
  583. if (focused != null)
  584. focused.HasFocus = false;
  585. focused = view;
  586. focused.HasFocus = true;
  587. focused.EnsureFocus ();
  588. }
  589. public override bool ProcessKey (KeyEvent kb)
  590. {
  591. if (Focused?.ProcessKey (kb) == true)
  592. return true;
  593. return false;
  594. }
  595. public override bool ProcessHotKey (KeyEvent kb)
  596. {
  597. if (subviews == null || subviews.Count == 0)
  598. return false;
  599. foreach (var view in subviews)
  600. if (view.ProcessHotKey (kb))
  601. return true;
  602. return false;
  603. }
  604. public override bool ProcessColdKey (KeyEvent kb)
  605. {
  606. if (subviews == null || subviews.Count == 0)
  607. return false;
  608. foreach (var view in subviews)
  609. if (view.ProcessColdKey (kb))
  610. return true;
  611. return false;
  612. }
  613. /// <summary>
  614. /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
  615. /// </summary>
  616. public void EnsureFocus ()
  617. {
  618. if (focused == null)
  619. FocusFirst ();
  620. }
  621. /// <summary>
  622. /// Focuses the first focusable subview if one exists.
  623. /// </summary>
  624. public void FocusFirst ()
  625. {
  626. if (subviews == null) {
  627. SuperView.SetFocus (this);
  628. return;
  629. }
  630. foreach (var view in subviews) {
  631. if (view.CanFocus) {
  632. SetFocus (view);
  633. return;
  634. }
  635. }
  636. }
  637. /// <summary>
  638. /// Focuses the last focusable subview if one exists.
  639. /// </summary>
  640. public void FocusLast ()
  641. {
  642. if (subviews == null)
  643. return;
  644. for (int i = subviews.Count; i > 0;) {
  645. i--;
  646. View v = subviews [i];
  647. if (v.CanFocus) {
  648. SetFocus (v);
  649. return;
  650. }
  651. }
  652. }
  653. /// <summary>
  654. /// Focuses the previous view.
  655. /// </summary>
  656. /// <returns><c>true</c>, if previous was focused, <c>false</c> otherwise.</returns>
  657. public bool FocusPrev ()
  658. {
  659. if (subviews == null || subviews.Count == 0)
  660. return false;
  661. if (focused == null) {
  662. FocusLast ();
  663. return true;
  664. }
  665. int focused_idx = -1;
  666. for (int i = subviews.Count; i > 0;) {
  667. i--;
  668. View w = subviews [i];
  669. if (w.HasFocus) {
  670. if (w.FocusPrev ())
  671. return true;
  672. focused_idx = i;
  673. continue;
  674. }
  675. if (w.CanFocus && focused_idx != -1) {
  676. focused.HasFocus = false;
  677. if (w.CanFocus)
  678. w.FocusLast ();
  679. SetFocus (w);
  680. return true;
  681. }
  682. }
  683. if (focused_idx != -1) {
  684. FocusLast ();
  685. return true;
  686. }
  687. if (focused != null) {
  688. focused.HasFocus = false;
  689. focused = null;
  690. }
  691. return false;
  692. }
  693. /// <summary>
  694. /// Focuses the next view.
  695. /// </summary>
  696. /// <returns><c>true</c>, if next was focused, <c>false</c> otherwise.</returns>
  697. public bool FocusNext ()
  698. {
  699. if (subviews == null || subviews.Count == 0)
  700. return false;
  701. if (focused == null) {
  702. FocusFirst ();
  703. return focused != null;
  704. }
  705. int n = subviews.Count;
  706. int focused_idx = -1;
  707. for (int i = 0; i < n; i++) {
  708. View w = subviews [i];
  709. if (w.HasFocus) {
  710. if (w.FocusNext ())
  711. return true;
  712. focused_idx = i;
  713. continue;
  714. }
  715. if (w.CanFocus && focused_idx != -1) {
  716. focused.HasFocus = false;
  717. if (w != null && w.CanFocus)
  718. w.FocusFirst ();
  719. SetFocus (w);
  720. return true;
  721. }
  722. }
  723. if (focused != null) {
  724. focused.HasFocus = false;
  725. focused = null;
  726. }
  727. return false;
  728. }
  729. /// <summary>
  730. /// This virtual method is invoked when a view starts executing or
  731. /// when the dimensions of the view have changed, for example in
  732. /// response to the container view or terminal resizing.
  733. /// </summary>
  734. public virtual void LayoutSubviews ()
  735. {
  736. }
  737. /// <summary>
  738. /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.View"/>.
  739. /// </summary>
  740. /// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.View"/>.</returns>
  741. public override string ToString ()
  742. {
  743. return $"{GetType ().Name}({Id})({Frame})";
  744. }
  745. }
  746. /// <summary>
  747. /// Toplevel views can be modally executed.
  748. /// </summary>
  749. /// <remarks>
  750. /// <para>
  751. /// Toplevels can be modally executing views, and they return control
  752. /// to the caller when the "Running" property is set to false.
  753. /// </para>
  754. /// </remarks>
  755. public class Toplevel : View {
  756. /// <summary>
  757. /// This flag is checked on each iteration of the mainloop and it continues
  758. /// running until this flag is set to false.
  759. /// </summary>
  760. public bool Running;
  761. /// <summary>
  762. /// Initializes a new instance of the <see cref="T:Terminal.Toplevel"/> class.
  763. /// </summary>
  764. /// <param name="frame">Frame.</param>
  765. public Toplevel (Rect frame) : base (frame)
  766. {
  767. ColorScheme = Colors.Base;
  768. }
  769. /// <summary>
  770. /// Convenience factory method that creates a new toplevel with the current terminal dimensions.
  771. /// </summary>
  772. /// <returns>The create.</returns>
  773. public static Toplevel Create ()
  774. {
  775. return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
  776. }
  777. public override bool CanFocus {
  778. get => true;
  779. }
  780. public override bool ProcessKey (KeyEvent kb)
  781. {
  782. if (base.ProcessKey (kb))
  783. return true;
  784. switch (kb.Key) {
  785. case Key.ControlC:
  786. // TODO: stop current execution of this container
  787. break;
  788. case Key.ControlZ:
  789. Driver.Suspend ();
  790. return true;
  791. #if false
  792. case Key.F5:
  793. Application.DebugDrawBounds = !Application.DebugDrawBounds;
  794. SetNeedsDisplay ();
  795. return true;
  796. #endif
  797. case Key.Tab:
  798. var old = Focused;
  799. if (!FocusNext ())
  800. FocusNext ();
  801. if (old != Focused) {
  802. old?.SetNeedsDisplay ();
  803. Focused?.SetNeedsDisplay ();
  804. }
  805. return true;
  806. case Key.BackTab:
  807. old = Focused;
  808. if (!FocusPrev ())
  809. FocusPrev ();
  810. if (old != Focused) {
  811. old?.SetNeedsDisplay ();
  812. Focused?.SetNeedsDisplay ();
  813. }
  814. return true;
  815. case Key.ControlL:
  816. Application.Refresh ();
  817. return true;
  818. }
  819. return false;
  820. }
  821. }
  822. /// <summary>
  823. /// A toplevel view that draws a frame around its region and has a "ContentView" subview where the contents are added.
  824. /// </summary>
  825. public class Window : Toplevel, IEnumerable {
  826. View contentView;
  827. string title;
  828. /// <summary>
  829. /// The title to be displayed for this window.
  830. /// </summary>
  831. /// <value>The title.</value>
  832. public string Title {
  833. get => title;
  834. set {
  835. title = value;
  836. SetNeedsDisplay ();
  837. }
  838. }
  839. class ContentView : View {
  840. public ContentView (Rect frame) : base (frame) { }
  841. }
  842. /// <summary>
  843. /// Initializes a new instance of the <see cref="T:Terminal.Window"/> class with an optioanl title
  844. /// </summary>
  845. /// <param name="frame">Frame.</param>
  846. /// <param name="title">Title.</param>
  847. public Window (Rect frame, string title = null) : this (frame, title, padding: 0)
  848. {
  849. }
  850. int padding;
  851. /// <summary>
  852. /// Initializes a new instance of the <see cref="T:Terminal.Window"/> with
  853. /// the specified frame for its location, with the specified border
  854. /// an optional title.
  855. /// </summary>
  856. /// <param name="frame">Frame.</param>
  857. /// <param name="padding">Number of characters to use for padding of the drawn frame.</param>
  858. /// <param name="title">Title.</param>
  859. public Window (Rect frame, string title = null, int padding = 0) : base (frame)
  860. {
  861. this.Title = title;
  862. int wb = 2 * (1 + padding);
  863. this.padding = padding;
  864. var cFrame = new Rect (1 + padding, 1 + padding, frame.Width - wb, frame.Height - wb);
  865. contentView = new ContentView (cFrame);
  866. base.Add (contentView);
  867. }
  868. /// <summary>
  869. /// Enumerates the various views in the ContentView.
  870. /// </summary>
  871. /// <returns>The enumerator.</returns>
  872. public new IEnumerator GetEnumerator ()
  873. {
  874. return contentView.GetEnumerator ();
  875. }
  876. void DrawFrame ()
  877. {
  878. DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), padding, fill: true);
  879. }
  880. /// <summary>
  881. /// Add the specified view to the ContentView.
  882. /// </summary>
  883. /// <param name="view">View to add to the window.</param>
  884. public override void Add (View view)
  885. {
  886. contentView.Add (view);
  887. }
  888. public override void Redraw (Rect bounds)
  889. {
  890. if (!NeedDisplay.IsEmpty) {
  891. Driver.SetAttribute (ColorScheme.Normal);
  892. DrawFrame ();
  893. if (HasFocus)
  894. Driver.SetAttribute (ColorScheme.Normal);
  895. var width = Frame.Width;
  896. if (Title != null && width > 4) {
  897. Move (1+padding, padding);
  898. Driver.AddCh (' ');
  899. var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
  900. Driver.AddStr (str);
  901. Driver.AddCh (' ');
  902. }
  903. Driver.SetAttribute (ColorScheme.Normal);
  904. }
  905. contentView.Redraw (contentView.Bounds);
  906. ClearNeedsDisplay ();
  907. }
  908. #if false
  909. //
  910. // It does not look like the event is raised on clicked-drag
  911. // need to figure that out.
  912. //
  913. Point? dragPosition;
  914. public override bool MouseEvent(MouseEvent me)
  915. {
  916. if (me.Flags == MouseFlags.Button1Pressed){
  917. if (dragPosition.HasValue) {
  918. var dx = me.X - dragPosition.Value.X;
  919. var dy = me.Y - dragPosition.Value.Y;
  920. var nx = Frame.X + dx;
  921. var ny = Frame.Y + dy;
  922. if (nx < 0)
  923. nx = 0;
  924. if (ny < 0)
  925. ny = 0;
  926. Demo.ml2.Text = $"{dx},{dy}";
  927. dragPosition = new Point (me.X, me.Y);
  928. // TODO: optimize, only SetNeedsDisplay on the before/after regions.
  929. if (SuperView == null)
  930. Application.Refresh ();
  931. else
  932. SuperView.SetNeedsDisplay ();
  933. Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
  934. SetNeedsDisplay ();
  935. return true;
  936. } else {
  937. dragPosition = new Point (me.X, me.Y);
  938. Application.GrabMouse (this);
  939. Demo.ml2.Text = $"Starting at {dragPosition}";
  940. return true;
  941. }
  942. }
  943. if (me.Flags == MouseFlags.Button1Released) {
  944. Application.UngrabMouse ();
  945. dragPosition = null;
  946. //Driver.StopReportingMouseMoves ();
  947. }
  948. Demo.ml.Text = me.ToString ();
  949. return false;
  950. }
  951. #endif
  952. }
  953. /// <summary>
  954. /// The application driver for gui.cs
  955. /// </summary>
  956. /// <remarks>
  957. /// <para>
  958. /// You can hook up to the Iteration event to have your method
  959. /// invoked on each iteration of the mainloop.
  960. /// </para>
  961. /// <para>
  962. /// Creates a mainloop to process input events, handle timers and
  963. /// other sources of data. It is accessible via the MainLoop property.
  964. /// </para>
  965. /// <para>
  966. /// When invoked sets the SynchronizationContext to one that is tied
  967. /// to the mainloop, allowing user code to use async/await.
  968. /// </para>
  969. /// </remarks>
  970. public class Application {
  971. /// <summary>
  972. /// The current Console Driver in use.
  973. /// </summary>
  974. public static ConsoleDriver Driver = new CursesDriver ();
  975. /// <summary>
  976. /// The Toplevel object used for the application on startup.
  977. /// </summary>
  978. /// <value>The top.</value>
  979. public static Toplevel Top { get; private set; }
  980. /// <summary>
  981. /// The current toplevel object. This is updated when Application.Run enters and leaves and points to the current toplevel.
  982. /// </summary>
  983. /// <value>The current.</value>
  984. public static Toplevel Current { get; private set; }
  985. /// <summary>
  986. /// The mainloop driver for the applicaiton
  987. /// </summary>
  988. /// <value>The main loop.</value>
  989. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  990. static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
  991. /// <summary>
  992. /// This event is raised on each iteration of the
  993. /// main loop.
  994. /// </summary>
  995. /// <remarks>
  996. /// See also <see cref="Timeout"/>
  997. /// </remarks>
  998. static public event EventHandler Iteration;
  999. /// <summary>
  1000. /// Returns a rectangle that is centered in the screen for the provided size.
  1001. /// </summary>
  1002. /// <returns>The centered rect.</returns>
  1003. /// <param name="size">Size for the rectangle.</param>
  1004. public static Rect MakeCenteredRect (Size size)
  1005. {
  1006. return new Rect (new Point ((Driver.Cols - size.Width) / 2, (Driver.Rows - size.Height) / 2), size);
  1007. }
  1008. //
  1009. // provides the sync context set while executing code in gui.cs, to let
  1010. // users use async/await on their code
  1011. //
  1012. class MainLoopSyncContext : SynchronizationContext {
  1013. Mono.Terminal.MainLoop mainLoop;
  1014. public MainLoopSyncContext (Mono.Terminal.MainLoop mainLoop)
  1015. {
  1016. this.mainLoop = mainLoop;
  1017. }
  1018. public override SynchronizationContext CreateCopy ()
  1019. {
  1020. return new MainLoopSyncContext (MainLoop);
  1021. }
  1022. public override void Post (SendOrPostCallback d, object state)
  1023. {
  1024. mainLoop.AddIdle (() => {
  1025. d (state);
  1026. return false;
  1027. });
  1028. }
  1029. public override void Send (SendOrPostCallback d, object state)
  1030. {
  1031. mainLoop.Invoke (() => {
  1032. d (state);
  1033. });
  1034. }
  1035. }
  1036. /// <summary>
  1037. /// Initializes the Application
  1038. /// </summary>
  1039. public static void Init ()
  1040. {
  1041. if (Top != null)
  1042. return;
  1043. Driver.Init (TerminalResized);
  1044. MainLoop = new Mono.Terminal.MainLoop ();
  1045. SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
  1046. Top = Toplevel.Create ();
  1047. Current = Top;
  1048. }
  1049. /// <summary>
  1050. /// Captures the execution state for the provided TopLevel view.
  1051. /// </summary>
  1052. public class RunState : IDisposable {
  1053. internal RunState (Toplevel view)
  1054. {
  1055. Toplevel = view;
  1056. }
  1057. internal Toplevel Toplevel;
  1058. /// <summary>
  1059. /// Releases all resource used by the <see cref="T:Terminal.Application.RunState"/> object.
  1060. /// </summary>
  1061. /// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="T:Terminal.Application.RunState"/>. The
  1062. /// <see cref="Dispose"/> method leaves the <see cref="T:Terminal.Application.RunState"/> in an unusable state. After
  1063. /// calling <see cref="Dispose"/>, you must release all references to the
  1064. /// <see cref="T:Terminal.Application.RunState"/> so the garbage collector can reclaim the memory that the
  1065. /// <see cref="T:Terminal.Application.RunState"/> was occupying.</remarks>
  1066. public void Dispose ()
  1067. {
  1068. Dispose (true);
  1069. GC.SuppressFinalize (this);
  1070. }
  1071. /// <summary>
  1072. /// Dispose the specified disposing.
  1073. /// </summary>
  1074. /// <returns>The dispose.</returns>
  1075. /// <param name="disposing">If set to <c>true</c> disposing.</param>
  1076. public virtual void Dispose (bool disposing)
  1077. {
  1078. if (Toplevel != null) {
  1079. Application.End (Toplevel);
  1080. Toplevel = null;
  1081. }
  1082. }
  1083. }
  1084. static void ProcessKeyEvent (KeyEvent ke)
  1085. {
  1086. if (Current.ProcessHotKey (ke))
  1087. return;
  1088. if (Current.ProcessKey (ke))
  1089. return;
  1090. // Process the key normally
  1091. if (Current.ProcessColdKey (ke))
  1092. return;
  1093. }
  1094. static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
  1095. {
  1096. var startFrame = start.Frame;
  1097. if (!startFrame.Contains (x, y)) {
  1098. resx = 0;
  1099. resy = 0;
  1100. return null;
  1101. }
  1102. if (start.Subviews != null){
  1103. int count = start.Subviews.Count;
  1104. if (count > 0) {
  1105. var rx = x - startFrame.X;
  1106. var ry = y - startFrame.Y;
  1107. for (int i = count - 1; i >= 0; i--) {
  1108. View v = start.Subviews [i];
  1109. if (v.Frame.Contains (rx, ry)) {
  1110. var deep = FindDeepestView (v, rx, ry, out resx, out resy);
  1111. if (deep == null)
  1112. return v;
  1113. return deep;
  1114. }
  1115. }
  1116. }
  1117. }
  1118. resx = x-startFrame.X;
  1119. resy = y-startFrame.Y;
  1120. return start;
  1121. }
  1122. static View mouseGrabView;
  1123. /// <summary>
  1124. /// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called.
  1125. /// </summary>
  1126. /// <returns>The grab.</returns>
  1127. /// <param name="view">View that will receive all mouse events until UngrabMouse is invoked.</param>
  1128. public static void GrabMouse (View view)
  1129. {
  1130. if (view == null)
  1131. return;
  1132. mouseGrabView = view;
  1133. }
  1134. /// <summary>
  1135. /// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
  1136. /// </summary>
  1137. public static void UngrabMouse ()
  1138. {
  1139. mouseGrabView = null;
  1140. }
  1141. /// <summary>
  1142. /// Merely a debugging aid to see the raw mouse events
  1143. /// </summary>
  1144. static public Action<MouseEvent> RootMouseEvent;
  1145. static void ProcessMouseEvent (MouseEvent me)
  1146. {
  1147. RootMouseEvent?.Invoke (me);
  1148. if (mouseGrabView != null) {
  1149. var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
  1150. var nme = new MouseEvent () {
  1151. X = newxy.X,
  1152. Y = newxy.Y,
  1153. Flags = me.Flags
  1154. };
  1155. mouseGrabView.MouseEvent (me);
  1156. return;
  1157. }
  1158. int rx, ry;
  1159. var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
  1160. if (view != null) {
  1161. if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition)
  1162. return;
  1163. var nme = new MouseEvent () {
  1164. X = rx,
  1165. Y = ry,
  1166. Flags = me.Flags
  1167. };
  1168. // Should we bubbled up the event, if it is not handled?
  1169. view.MouseEvent (nme);
  1170. }
  1171. }
  1172. static public RunState Begin (Toplevel toplevel)
  1173. {
  1174. if (toplevel == null)
  1175. throw new ArgumentNullException (nameof (toplevel));
  1176. var rs = new RunState (toplevel);
  1177. Init ();
  1178. toplevels.Push (toplevel);
  1179. Current = toplevel;
  1180. Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
  1181. toplevel.LayoutSubviews ();
  1182. toplevel.FocusFirst ();
  1183. Redraw (toplevel);
  1184. toplevel.PositionCursor ();
  1185. Driver.Refresh ();
  1186. return rs;
  1187. }
  1188. static public void End (RunState rs)
  1189. {
  1190. if (rs == null)
  1191. throw new ArgumentNullException (nameof (rs));
  1192. rs.Dispose ();
  1193. }
  1194. static void Shutdown ()
  1195. {
  1196. Driver.End ();
  1197. }
  1198. static void Redraw (View view)
  1199. {
  1200. view.Redraw (view.Bounds);
  1201. Driver.Refresh ();
  1202. }
  1203. static void Refresh (View view)
  1204. {
  1205. view.Redraw (view.Bounds);
  1206. Driver.Refresh ();
  1207. }
  1208. /// <summary>
  1209. /// Triggers a refresh of the entire display.
  1210. /// </summary>
  1211. public static void Refresh ()
  1212. {
  1213. Driver.RedrawTop ();
  1214. View last = null;
  1215. foreach (var v in toplevels.Reverse ()) {
  1216. v.SetNeedsDisplay ();
  1217. v.Redraw (v.Bounds);
  1218. last = v;
  1219. }
  1220. last?.PositionCursor ();
  1221. Driver.Refresh ();
  1222. }
  1223. internal static void End (View view)
  1224. {
  1225. if (toplevels.Peek () != view)
  1226. throw new ArgumentException ("The view that you end with must be balanced");
  1227. toplevels.Pop ();
  1228. if (toplevels.Count == 0)
  1229. Shutdown ();
  1230. else {
  1231. Current = toplevels.Peek () as Toplevel;
  1232. Refresh ();
  1233. }
  1234. }
  1235. /// <summary>
  1236. /// Runs the main loop for the created dialog
  1237. /// </summary>
  1238. /// <remarks>
  1239. /// Use the wait parameter to control whether this is a
  1240. /// blocking or non-blocking call.
  1241. /// </remarks>
  1242. public static void RunLoop (RunState state, bool wait = true)
  1243. {
  1244. if (state == null)
  1245. throw new ArgumentNullException (nameof (state));
  1246. if (state.Toplevel == null)
  1247. throw new ObjectDisposedException ("state");
  1248. for (state.Toplevel.Running = true; state.Toplevel.Running;) {
  1249. if (MainLoop.EventsPending (wait)) {
  1250. MainLoop.MainIteration ();
  1251. if (Iteration != null)
  1252. Iteration (null, EventArgs.Empty);
  1253. } else if (wait == false)
  1254. return;
  1255. if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
  1256. state.Toplevel.Redraw (state.Toplevel.Bounds);
  1257. if (DebugDrawBounds)
  1258. DrawBounds (state.Toplevel);
  1259. state.Toplevel.PositionCursor ();
  1260. Driver.Refresh ();
  1261. }
  1262. }
  1263. }
  1264. internal static bool DebugDrawBounds;
  1265. // Need to look into why this does not work properly.
  1266. static void DrawBounds (View v)
  1267. {
  1268. v.DrawFrame (v.Frame, padding: 0, fill: false);
  1269. if (v.Subviews != null && v.Subviews.Count > 0)
  1270. foreach (var sub in v.Subviews)
  1271. DrawBounds (sub);
  1272. }
  1273. /// <summary>
  1274. /// Runs the application with the built-in toplevel view
  1275. /// </summary>
  1276. public static void Run ()
  1277. {
  1278. Run (Top);
  1279. }
  1280. /// <summary>
  1281. /// Runs the main loop on the given container.
  1282. /// </summary>
  1283. /// <remarks>
  1284. /// <para>
  1285. /// This method is used to start processing events
  1286. /// for the main application, but it is also used to
  1287. /// run modal dialog boxes.
  1288. /// </para>
  1289. /// <para>
  1290. /// To make a toplevel stop execution, set the "Running"
  1291. /// property to false.
  1292. /// </para>
  1293. /// </remarks>
  1294. public static void Run (Toplevel view)
  1295. {
  1296. var runToken = Begin (view);
  1297. RunLoop (runToken);
  1298. End (runToken);
  1299. }
  1300. /// <summary>
  1301. /// Stops running the most recent toplevel
  1302. /// </summary>
  1303. public static void RequestStop ()
  1304. {
  1305. var ct = Current as Toplevel;
  1306. Current.Running = false;
  1307. }
  1308. static void TerminalResized ()
  1309. {
  1310. foreach (var t in toplevels) {
  1311. t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
  1312. }
  1313. }
  1314. }
  1315. }