Core.cs 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159
  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. public class View : Responder, IEnumerable {
  108. string id = "";
  109. View container = null;
  110. View focused = null;
  111. public static ConsoleDriver Driver = Application.Driver;
  112. public static IList<View> empty = new List<View> (0).AsReadOnly ();
  113. List<View> subviews;
  114. public IList<View> Subviews => subviews == null ? empty : subviews.AsReadOnly ();
  115. internal Rect NeedDisplay { get; private set; } = Rect.Empty;
  116. // The frame for the object
  117. Rect frame;
  118. public string Id {
  119. get => id;
  120. set {
  121. id = value;
  122. }
  123. }
  124. // The frame for this view
  125. public Rect Frame {
  126. get => frame;
  127. set {
  128. if (SuperView != null) {
  129. SuperView.SetNeedsDisplay (frame);
  130. SuperView.SetNeedsDisplay (value);
  131. }
  132. frame = value;
  133. SetNeedsDisplay (frame);
  134. }
  135. }
  136. public IEnumerator GetEnumerator ()
  137. {
  138. foreach (var v in subviews)
  139. yield return v;
  140. }
  141. public Rect Bounds {
  142. get => new Rect (Point.Empty, Frame.Size);
  143. set {
  144. Frame = new Rect (frame.Location, value.Size);
  145. }
  146. }
  147. public View SuperView => container;
  148. public View (Rect frame)
  149. {
  150. this.Frame = frame;
  151. CanFocus = false;
  152. }
  153. /// <summary>
  154. /// Invoke to flag that this view needs to be redisplayed, by any code
  155. /// that alters the state of the view.
  156. /// </summary>
  157. public void SetNeedsDisplay ()
  158. {
  159. SetNeedsDisplay (Frame);
  160. }
  161. public void SetNeedsDisplay (Rect region)
  162. {
  163. if (NeedDisplay.IsEmpty)
  164. NeedDisplay = region;
  165. else {
  166. var x = Math.Min (NeedDisplay.X, region.X);
  167. var y = Math.Min (NeedDisplay.Y, region.Y);
  168. var w = Math.Max (NeedDisplay.Width, region.Width);
  169. var h = Math.Max (NeedDisplay.Height, region.Height);
  170. NeedDisplay = new Rect (x, y, w, h);
  171. }
  172. if (container != null)
  173. container.ChildNeedsDisplay ();
  174. if (subviews == null)
  175. return;
  176. foreach (var view in subviews)
  177. if (view.Frame.IntersectsWith (region)) {
  178. view.SetNeedsDisplay (Rect.Intersect (view.Frame, region));
  179. }
  180. }
  181. internal bool childNeedsDisplay;
  182. public void ChildNeedsDisplay ()
  183. {
  184. childNeedsDisplay = true;
  185. if (container != null)
  186. container.ChildNeedsDisplay ();
  187. }
  188. /// <summary>
  189. /// Adds a subview to this view.
  190. /// </summary>
  191. /// <remarks>
  192. /// </remarks>
  193. public virtual void Add (View view)
  194. {
  195. if (view == null)
  196. return;
  197. if (subviews == null)
  198. subviews = new List<View> ();
  199. subviews.Add (view);
  200. view.container = this;
  201. if (view.CanFocus)
  202. CanFocus = true;
  203. SetNeedsDisplay ();
  204. }
  205. public void Add (params View [] views)
  206. {
  207. if (views == null)
  208. return;
  209. foreach (var view in views)
  210. Add (view);
  211. }
  212. /// <summary>
  213. /// Removes all the widgets from this container.
  214. /// </summary>
  215. /// <remarks>
  216. /// </remarks>
  217. public virtual void RemoveAll ()
  218. {
  219. if (subviews == null)
  220. return;
  221. while (subviews.Count > 0) {
  222. var view = subviews [0];
  223. Remove (view);
  224. subviews.RemoveAt (0);
  225. }
  226. }
  227. /// <summary>
  228. /// Removes a widget from this container.
  229. /// </summary>
  230. /// <remarks>
  231. /// </remarks>
  232. public virtual void Remove (View view)
  233. {
  234. if (view == null)
  235. return;
  236. SetNeedsDisplay ();
  237. var touched = view.Frame;
  238. subviews.Remove (view);
  239. view.container = null;
  240. if (subviews.Count < 1)
  241. this.CanFocus = false;
  242. foreach (var v in subviews) {
  243. if (v.Frame.IntersectsWith (touched))
  244. view.SetNeedsDisplay ();
  245. }
  246. }
  247. /// <summary>
  248. /// Clears the view region with the current color.
  249. /// </summary>
  250. /// <remarks>
  251. /// <para>
  252. /// This clears the entire region used by this view.
  253. /// </para>
  254. /// </remarks>
  255. public void Clear ()
  256. {
  257. var h = Frame.Height;
  258. var w = Frame.Width;
  259. for (int line = 0; line < h; line++) {
  260. Move (0, line);
  261. for (int col = 0; col < w; col++)
  262. Driver.AddCh (' ');
  263. }
  264. }
  265. /// <summary>
  266. /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
  267. /// </summary>
  268. /// <param name="col">View-based column.</param>
  269. /// <param name="row">View-based row.</param>
  270. /// <param name="rcol">Absolute column, display relative.</param>
  271. /// <param name="rrow">Absolute row, display relative.</param>
  272. internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  273. {
  274. // Computes the real row, col relative to the screen.
  275. rrow = row + frame.Y;
  276. rcol = col + frame.X;
  277. var ccontainer = container;
  278. while (ccontainer != null) {
  279. rrow += ccontainer.frame.Y;
  280. rcol += ccontainer.frame.X;
  281. ccontainer = ccontainer.container;
  282. }
  283. // The following ensures that the cursor is always in the screen boundaries.
  284. if (clipped) {
  285. rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
  286. rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
  287. }
  288. }
  289. // Converts a rectangle in view coordinates to screen coordinates.
  290. Rect RectToScreen (Rect rect)
  291. {
  292. ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
  293. return new Rect (x, y, rect.Width, rect.Height);
  294. }
  295. // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
  296. Rect ScreenClip (Rect rect)
  297. {
  298. var x = rect.X < 0 ? 0 : rect.X;
  299. var y = rect.Y < 0 ? 0 : rect.Y;
  300. var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
  301. var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
  302. return new Rect (x, y, w, h);
  303. }
  304. /// <summary>
  305. /// Draws a frame in the current view, clipped by the boundary of this view
  306. /// </summary>
  307. /// <param name="rect">Rectangular region for the frame to be drawn.</param>
  308. /// <param name="fill">If set to <c>true</c> it fill will the contents.</param>
  309. public void DrawFrame (Rect rect, bool fill = false)
  310. {
  311. var scrRect = RectToScreen (rect);
  312. var savedClip = Driver.Clip;
  313. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  314. Driver.DrawFrame (scrRect, fill);
  315. Driver.Clip = savedClip;
  316. }
  317. /// <summary>
  318. /// Utility function to draw strings that contain a hotkey
  319. /// </summary>
  320. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  321. /// <param name="hotColor">Hot color.</param>
  322. /// <param name="normalColor">Normal color.</param>
  323. public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
  324. {
  325. Driver.SetAttribute (normalColor);
  326. foreach (var c in text) {
  327. if (c == '_') {
  328. Driver.SetAttribute (hotColor);
  329. continue;
  330. }
  331. Driver.AddCh (c);
  332. Driver.SetAttribute (normalColor);
  333. }
  334. }
  335. /// <summary>
  336. /// Utility function to draw strings that contains a hotkey using a colorscheme and the "focused" state.
  337. /// </summary>
  338. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  339. /// <param name="focused">If set to <c>true</c> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
  340. /// <param name="scheme">The color scheme to use.</param>
  341. public void DrawHotString (string text, bool focused, ColorScheme scheme)
  342. {
  343. if (focused)
  344. DrawHotString (text, scheme.HotFocus, scheme.Focus);
  345. else
  346. DrawHotString (text, scheme.HotNormal, scheme.Normal);
  347. }
  348. /// <summary>
  349. /// This moves the cursor to the specified column and row in the view.
  350. /// </summary>
  351. /// <returns>The move.</returns>
  352. /// <param name="col">Col.</param>
  353. /// <param name="row">Row.</param>
  354. public void Move (int col, int row)
  355. {
  356. ViewToScreen (col, row, out var rcol, out var rrow);
  357. Driver.Move (rcol, rrow);
  358. }
  359. /// <summary>
  360. /// Positions the cursor in the right position based on the currently focused view in the chain.
  361. /// </summary>
  362. public virtual void PositionCursor ()
  363. {
  364. if (focused != null)
  365. focused.PositionCursor ();
  366. else
  367. Move (frame.X, frame.Y);
  368. }
  369. public override bool HasFocus {
  370. get {
  371. return base.HasFocus;
  372. }
  373. internal set {
  374. if (base.HasFocus != value)
  375. SetNeedsDisplay ();
  376. base.HasFocus = value;
  377. }
  378. }
  379. /// <summary>
  380. /// Returns the currently focused view inside this view, or null if nothing is focused.
  381. /// </summary>
  382. /// <value>The focused.</value>
  383. public View Focused => focused;
  384. public View MostFocused {
  385. get {
  386. if (Focused == null)
  387. return null;
  388. var most = Focused.MostFocused;
  389. if (most != null)
  390. return most;
  391. return Focused;
  392. }
  393. }
  394. /// <summary>
  395. /// Displays the specified character in the specified column and row.
  396. /// </summary>
  397. /// <param name="col">Col.</param>
  398. /// <param name="row">Row.</param>
  399. /// <param name="ch">Ch.</param>
  400. public void AddCh (int col, int row, int ch)
  401. {
  402. if (row < 0 || col < 0)
  403. return;
  404. if (row > frame.Height - 1 || col > frame.Width - 1)
  405. return;
  406. Move (col, row);
  407. Driver.AddCh (ch);
  408. }
  409. protected void ClearNeedsDisplay ()
  410. {
  411. NeedDisplay = Rect.Empty;
  412. childNeedsDisplay = false;
  413. }
  414. /// <summary>
  415. /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
  416. /// </summary>
  417. /// <remarks>
  418. /// The region argument is relative to the view itself.
  419. /// </remarks>
  420. public virtual void Redraw (Rect region)
  421. {
  422. var clipRect = new Rect (Point.Empty, frame.Size);
  423. if (subviews != null) {
  424. foreach (var view in subviews) {
  425. if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
  426. if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
  427. // TODO: optimize this by computing the intersection of region and view.Bounds
  428. view.Redraw (view.Bounds);
  429. }
  430. view.NeedDisplay = Rect.Empty;
  431. view.childNeedsDisplay = false;
  432. }
  433. }
  434. }
  435. ClearNeedsDisplay ();
  436. }
  437. /// <summary>
  438. /// Focuses the specified sub-view.
  439. /// </summary>
  440. /// <param name="view">View.</param>
  441. public void SetFocus (View view)
  442. {
  443. if (view == null)
  444. return;
  445. //Console.WriteLine ($"Request to focus {view}");
  446. if (!view.CanFocus)
  447. return;
  448. if (focused == view)
  449. return;
  450. // Make sure that this view is a subview
  451. View c;
  452. for (c = view.container; c != null; c = c.container)
  453. if (c == this)
  454. break;
  455. if (c == null)
  456. throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
  457. if (focused != null)
  458. focused.HasFocus = false;
  459. focused = view;
  460. focused.HasFocus = true;
  461. focused.EnsureFocus ();
  462. }
  463. public override bool ProcessKey (KeyEvent kb)
  464. {
  465. if (Focused?.ProcessKey (kb) == true)
  466. return true;
  467. return false;
  468. }
  469. public override bool ProcessHotKey (KeyEvent kb)
  470. {
  471. if (subviews == null || subviews.Count == 0)
  472. return false;
  473. foreach (var view in subviews)
  474. if (view.ProcessHotKey (kb))
  475. return true;
  476. return false;
  477. }
  478. public override bool ProcessColdKey (KeyEvent kb)
  479. {
  480. if (subviews == null || subviews.Count == 0)
  481. return false;
  482. foreach (var view in subviews)
  483. if (view.ProcessColdKey (kb))
  484. return true;
  485. return false;
  486. }
  487. /// <summary>
  488. /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
  489. /// </summary>
  490. public void EnsureFocus ()
  491. {
  492. if (focused == null)
  493. FocusFirst ();
  494. }
  495. /// <summary>
  496. /// Focuses the first focusable subview if one exists.
  497. /// </summary>
  498. public void FocusFirst ()
  499. {
  500. if (subviews == null) {
  501. SuperView.SetFocus (this);
  502. return;
  503. }
  504. foreach (var view in subviews) {
  505. if (view.CanFocus) {
  506. SetFocus (view);
  507. return;
  508. }
  509. }
  510. }
  511. /// <summary>
  512. /// Focuses the last focusable subview if one exists.
  513. /// </summary>
  514. public void FocusLast ()
  515. {
  516. if (subviews == null)
  517. return;
  518. for (int i = subviews.Count; i > 0;) {
  519. i--;
  520. View v = subviews [i];
  521. if (v.CanFocus) {
  522. SetFocus (v);
  523. return;
  524. }
  525. }
  526. }
  527. /// <summary>
  528. /// Focuses the previous view.
  529. /// </summary>
  530. /// <returns><c>true</c>, if previous was focused, <c>false</c> otherwise.</returns>
  531. public bool FocusPrev ()
  532. {
  533. if (subviews == null || subviews.Count == 0)
  534. return false;
  535. if (focused == null) {
  536. FocusLast ();
  537. return true;
  538. }
  539. int focused_idx = -1;
  540. for (int i = subviews.Count; i > 0;) {
  541. i--;
  542. View w = subviews [i];
  543. if (w.HasFocus) {
  544. if (w.FocusPrev ())
  545. return true;
  546. focused_idx = i;
  547. continue;
  548. }
  549. if (w.CanFocus && focused_idx != -1) {
  550. focused.HasFocus = false;
  551. if (w.CanFocus)
  552. w.FocusLast ();
  553. SetFocus (w);
  554. return true;
  555. }
  556. }
  557. if (focused_idx != -1) {
  558. FocusLast ();
  559. return true;
  560. }
  561. if (focused != null) {
  562. focused.HasFocus = false;
  563. focused = null;
  564. }
  565. return false;
  566. }
  567. /// <summary>
  568. /// Focuses the next view.
  569. /// </summary>
  570. /// <returns><c>true</c>, if next was focused, <c>false</c> otherwise.</returns>
  571. public bool FocusNext ()
  572. {
  573. if (subviews == null || subviews.Count == 0)
  574. return false;
  575. if (focused == null) {
  576. FocusFirst ();
  577. return focused != null;
  578. }
  579. int n = subviews.Count;
  580. int focused_idx = -1;
  581. for (int i = 0; i < n; i++) {
  582. View w = subviews [i];
  583. if (w.HasFocus) {
  584. if (w.FocusNext ())
  585. return true;
  586. focused_idx = i;
  587. continue;
  588. }
  589. if (w.CanFocus && focused_idx != -1) {
  590. focused.HasFocus = false;
  591. if (w != null && w.CanFocus)
  592. w.FocusFirst ();
  593. SetFocus (w);
  594. return true;
  595. }
  596. }
  597. if (focused != null) {
  598. focused.HasFocus = false;
  599. focused = null;
  600. }
  601. return false;
  602. }
  603. public virtual void LayoutSubviews ()
  604. {
  605. }
  606. public override string ToString ()
  607. {
  608. return $"{GetType ().Name}({id})({Frame})";
  609. }
  610. }
  611. /// <summary>
  612. /// Toplevel views can be modally executed.
  613. /// </summary>
  614. /// <remarks>
  615. /// <para>
  616. /// Toplevels can be modally executing views, and they return control
  617. /// to the caller when the "Running" property is set to false.
  618. /// </para>
  619. /// </remarks>
  620. public class Toplevel : View {
  621. public bool Running;
  622. public Toplevel (Rect frame) : base (frame)
  623. {
  624. }
  625. public static Toplevel Create ()
  626. {
  627. return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
  628. }
  629. public override bool CanFocus {
  630. get => true;
  631. }
  632. public override bool ProcessKey (KeyEvent kb)
  633. {
  634. if (base.ProcessKey (kb))
  635. return true;
  636. switch (kb.Key) {
  637. case Key.ControlC:
  638. // TODO: stop current execution of this container
  639. break;
  640. case Key.ControlZ:
  641. // TODO: should suspend
  642. // console_csharp_send_sigtstp ();
  643. break;
  644. #if false
  645. case Key.F5:
  646. Application.DebugDrawBounds = !Application.DebugDrawBounds;
  647. SetNeedsDisplay ();
  648. return true;
  649. #endif
  650. case Key.Tab:
  651. var old = Focused;
  652. if (!FocusNext ())
  653. FocusNext ();
  654. if (old != Focused) {
  655. old?.SetNeedsDisplay ();
  656. Focused?.SetNeedsDisplay ();
  657. }
  658. return true;
  659. case Key.BackTab:
  660. old = Focused;
  661. if (!FocusPrev ())
  662. FocusPrev ();
  663. if (old != Focused) {
  664. old?.SetNeedsDisplay ();
  665. Focused?.SetNeedsDisplay ();
  666. }
  667. return true;
  668. case Key.ControlL:
  669. Application.Refresh ();
  670. return true;
  671. }
  672. return false;
  673. }
  674. }
  675. /// <summary>
  676. /// A toplevel view that draws a frame around its region and has a "ContentView" subview where the contents are added.
  677. /// </summary>
  678. public class Window : Toplevel, IEnumerable {
  679. View contentView;
  680. string title;
  681. public string Title {
  682. get => title;
  683. set {
  684. title = value;
  685. SetNeedsDisplay ();
  686. }
  687. }
  688. class ContentView : View {
  689. public ContentView (Rect frame) : base (frame) { }
  690. }
  691. /// <summary>
  692. /// Initializes a new instance of the <see cref="T:Terminal.Window"/> class with an optioanl title
  693. /// </summary>
  694. /// <param name="frame">Frame.</param>
  695. /// <param name="title">Title.</param>
  696. public Window (Rect frame, string title = null) : base (frame)
  697. {
  698. this.Title = title;
  699. frame.Inflate (-1, -1);
  700. contentView = new ContentView (frame);
  701. base.Add (contentView);
  702. }
  703. /// <summary>
  704. /// Enumerates the various views in the ContentView.
  705. /// </summary>
  706. /// <returns>The enumerator.</returns>
  707. public new IEnumerator GetEnumerator ()
  708. {
  709. return contentView.GetEnumerator ();
  710. }
  711. void DrawFrame ()
  712. {
  713. DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), true);
  714. }
  715. /// <summary>
  716. /// Add the specified view to the ContentView.
  717. /// </summary>
  718. /// <param name="view">View to add to the window.</param>
  719. public override void Add (View view)
  720. {
  721. contentView.Add (view);
  722. }
  723. public override void Redraw (Rect bounds)
  724. {
  725. if (!NeedDisplay.IsEmpty) {
  726. Driver.SetAttribute (Colors.Base.Normal);
  727. DrawFrame ();
  728. if (HasFocus)
  729. Driver.SetAttribute (Colors.Dialog.Normal);
  730. var width = Frame.Width;
  731. if (Title != null && width > 4) {
  732. Move (1, 0);
  733. Driver.AddCh (' ');
  734. var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
  735. Driver.AddStr (str);
  736. Driver.AddCh (' ');
  737. }
  738. Driver.SetAttribute (Colors.Dialog.Normal);
  739. }
  740. contentView.Redraw (contentView.Bounds);
  741. ClearNeedsDisplay ();
  742. }
  743. }
  744. /// <summary>
  745. /// The application driver for gui.cs
  746. /// </summary>
  747. /// <remarks>
  748. /// <para>
  749. /// You can hook up to the Iteration event to have your method
  750. /// invoked on each iteration of the mainloop.
  751. /// </para>
  752. /// <para>
  753. /// Creates a mainloop to process input events, handle timers and
  754. /// other sources of data. It is accessible via the MainLoop property.
  755. /// </para>
  756. /// <para>
  757. /// When invoked sets the SynchronizationContext to one that is tied
  758. /// to the mainloop, allowing user code to use async/await.
  759. /// </para>
  760. /// </remarks>
  761. public class Application {
  762. public static ConsoleDriver Driver = new CursesDriver ();
  763. public static Toplevel Top { get; private set; }
  764. public static View Current { get; private set; }
  765. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  766. static Stack<View> toplevels = new Stack<View> ();
  767. /// <summary>
  768. /// This event is raised on each iteration of the
  769. /// main loop.
  770. /// </summary>
  771. /// <remarks>
  772. /// See also <see cref="Timeout"/>
  773. /// </remarks>
  774. static public event EventHandler Iteration;
  775. /// <summary>
  776. /// Returns a rectangle that is centered in the screen for the provided size.
  777. /// </summary>
  778. /// <returns>The centered rect.</returns>
  779. /// <param name="size">Size for the rectangle.</param>
  780. public static Rect MakeCenteredRect (Size size)
  781. {
  782. return new Rect (new Point ((Driver.Cols - size.Width) / 2, (Driver.Rows - size.Height) / 2), size);
  783. }
  784. //
  785. // provides the sync context set while executing code in gui.cs, to let
  786. // users use async/await on their code
  787. //
  788. class MainLoopSyncContext : SynchronizationContext {
  789. Mono.Terminal.MainLoop mainLoop;
  790. public MainLoopSyncContext (Mono.Terminal.MainLoop mainLoop)
  791. {
  792. this.mainLoop = mainLoop;
  793. }
  794. public override SynchronizationContext CreateCopy ()
  795. {
  796. return new MainLoopSyncContext (MainLoop);
  797. }
  798. public override void Post (SendOrPostCallback d, object state)
  799. {
  800. mainLoop.AddIdle (() => {
  801. d (state);
  802. return false;
  803. });
  804. }
  805. public override void Send (SendOrPostCallback d, object state)
  806. {
  807. mainLoop.Invoke (() => {
  808. d (state);
  809. });
  810. }
  811. }
  812. /// <summary>
  813. /// Initializes the Application
  814. /// </summary>
  815. public static void Init ()
  816. {
  817. if (Top != null)
  818. return;
  819. Driver.Init (TerminalResized);
  820. MainLoop = new Mono.Terminal.MainLoop ();
  821. SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
  822. Top = Toplevel.Create ();
  823. Current = Top;
  824. }
  825. public class RunState : IDisposable {
  826. internal RunState (Toplevel view)
  827. {
  828. Toplevel = view;
  829. }
  830. internal Toplevel Toplevel;
  831. public void Dispose ()
  832. {
  833. Dispose (true);
  834. GC.SuppressFinalize (this);
  835. }
  836. public virtual void Dispose (bool disposing)
  837. {
  838. if (Toplevel != null) {
  839. Application.End (Toplevel);
  840. Toplevel = null;
  841. }
  842. }
  843. }
  844. static void ProcessKeyEvent (KeyEvent ke)
  845. {
  846. if (Current.ProcessHotKey (ke))
  847. return;
  848. if (Current.ProcessKey (ke))
  849. return;
  850. // Process the key normally
  851. if (Current.ProcessColdKey (ke))
  852. return;
  853. }
  854. static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
  855. {
  856. var startFrame = start.Frame;
  857. if (!startFrame.Contains (x, y)) {
  858. resx = 0;
  859. resy = 0;
  860. return null;
  861. }
  862. if (start.Subviews != null){
  863. int count = start.Subviews.Count;
  864. if (count > 0) {
  865. var rx = x - startFrame.X;
  866. var ry = y - startFrame.Y;
  867. for (int i = count - 1; i >= 0; i--) {
  868. View v = start.Subviews [i];
  869. if (v.Frame.Contains (rx, ry)) {
  870. var deep = FindDeepestView (v, rx, ry, out resx, out resy);
  871. if (deep == null)
  872. return v;
  873. return deep;
  874. }
  875. }
  876. }
  877. }
  878. resx = x-startFrame.X;
  879. resy = y-startFrame.Y;
  880. return start;
  881. }
  882. /// <summary>
  883. /// Merely a debugging aid to see the raw mouse events
  884. /// </summary>
  885. static public Action<MouseEvent> RootMouseEvent;
  886. static void ProcessMouseEvent (MouseEvent me)
  887. {
  888. RootMouseEvent?.Invoke (me);
  889. int rx, ry;
  890. var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
  891. if (view != null) {
  892. var nme = new MouseEvent () {
  893. X = rx,
  894. Y = ry,
  895. Flags = me.Flags
  896. };
  897. // Should we bubbled up the event, if it is not handled?
  898. view.MouseEvent (nme);
  899. }
  900. }
  901. static public RunState Begin (Toplevel toplevel)
  902. {
  903. if (toplevel == null)
  904. throw new ArgumentNullException (nameof (toplevel));
  905. var rs = new RunState (toplevel);
  906. Init ();
  907. toplevels.Push (toplevel);
  908. Current = toplevel;
  909. Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
  910. toplevel.LayoutSubviews ();
  911. toplevel.FocusFirst ();
  912. Redraw (toplevel);
  913. toplevel.PositionCursor ();
  914. Driver.Refresh ();
  915. return rs;
  916. }
  917. static public void End (RunState rs)
  918. {
  919. if (rs == null)
  920. throw new ArgumentNullException (nameof (rs));
  921. rs.Dispose ();
  922. }
  923. static void Shutdown ()
  924. {
  925. Driver.End ();
  926. }
  927. static void Redraw (View view)
  928. {
  929. view.Redraw (view.Bounds);
  930. Driver.Refresh ();
  931. }
  932. static void Refresh (View view)
  933. {
  934. view.Redraw (view.Bounds);
  935. Driver.Refresh ();
  936. }
  937. /// <summary>
  938. /// Triggers a refresh of the entire display.
  939. /// </summary>
  940. public static void Refresh ()
  941. {
  942. Driver.RedrawTop ();
  943. View last = null;
  944. foreach (var v in toplevels.Reverse ()) {
  945. v.SetNeedsDisplay ();
  946. v.Redraw (v.Bounds);
  947. last = v;
  948. }
  949. last?.PositionCursor ();
  950. Driver.Refresh ();
  951. }
  952. internal static void End (View view)
  953. {
  954. if (toplevels.Peek () != view)
  955. throw new ArgumentException ("The view that you end with must be balanced");
  956. toplevels.Pop ();
  957. if (toplevels.Count == 0)
  958. Shutdown ();
  959. else {
  960. Current = toplevels.Peek ();
  961. Refresh ();
  962. }
  963. }
  964. /// <summary>
  965. /// Runs the main loop for the created dialog
  966. /// </summary>
  967. /// <remarks>
  968. /// Use the wait parameter to control whether this is a
  969. /// blocking or non-blocking call.
  970. /// </remarks>
  971. public static void RunLoop (RunState state, bool wait = true)
  972. {
  973. if (state == null)
  974. throw new ArgumentNullException (nameof (state));
  975. if (state.Toplevel == null)
  976. throw new ObjectDisposedException ("state");
  977. for (state.Toplevel.Running = true; state.Toplevel.Running;) {
  978. if (MainLoop.EventsPending (wait)) {
  979. MainLoop.MainIteration ();
  980. if (Iteration != null)
  981. Iteration (null, EventArgs.Empty);
  982. } else if (wait == false)
  983. return;
  984. if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
  985. state.Toplevel.Redraw (state.Toplevel.Bounds);
  986. if (DebugDrawBounds)
  987. DrawBounds (state.Toplevel);
  988. state.Toplevel.PositionCursor ();
  989. Driver.Refresh ();
  990. }
  991. }
  992. }
  993. public static bool DebugDrawBounds;
  994. // Need to look into why this does not work properly.
  995. static void DrawBounds (View v)
  996. {
  997. v.DrawFrame (v.Frame, false);
  998. if (v.Subviews != null && v.Subviews.Count > 0)
  999. foreach (var sub in v.Subviews)
  1000. DrawBounds (sub);
  1001. }
  1002. public static void Run ()
  1003. {
  1004. Run (Top);
  1005. }
  1006. /// <summary>
  1007. /// Runs the main loop on the given container.
  1008. /// </summary>
  1009. /// <remarks>
  1010. /// <para>
  1011. /// This method is used to start processing events
  1012. /// for the main application, but it is also used to
  1013. /// run modal dialog boxes.
  1014. /// </para>
  1015. /// <para>
  1016. /// To make a toplevel stop execution, set the "Running"
  1017. /// property to false.
  1018. /// </para>
  1019. /// </remarks>
  1020. public static void Run (Toplevel view)
  1021. {
  1022. var runToken = Begin (view);
  1023. RunLoop (runToken);
  1024. End (runToken);
  1025. }
  1026. static void TerminalResized ()
  1027. {
  1028. foreach (var t in toplevels) {
  1029. t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
  1030. }
  1031. }
  1032. }
  1033. }