Core.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140
  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.ProcessHotKey (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. case Key.Tab:
  645. var old = Focused;
  646. if (!FocusNext ())
  647. FocusNext ();
  648. if (old != Focused) {
  649. old?.SetNeedsDisplay ();
  650. Focused?.SetNeedsDisplay ();
  651. }
  652. return true;
  653. case Key.BackTab:
  654. old = Focused;
  655. if (!FocusPrev ())
  656. FocusPrev ();
  657. if (old != Focused) {
  658. old?.SetNeedsDisplay ();
  659. Focused?.SetNeedsDisplay ();
  660. }
  661. return true;
  662. case Key.ControlL:
  663. Application.Refresh ();
  664. return true;
  665. }
  666. return false;
  667. }
  668. }
  669. /// <summary>
  670. /// A toplevel view that draws a frame around its region and has a "ContentView" subview where the contents are added.
  671. /// </summary>
  672. public class Window : Toplevel, IEnumerable {
  673. View contentView;
  674. string title;
  675. public string Title {
  676. get => title;
  677. set {
  678. title = value;
  679. SetNeedsDisplay ();
  680. }
  681. }
  682. class ContentView : View {
  683. public ContentView (Rect frame) : base (frame) { }
  684. }
  685. /// <summary>
  686. /// Initializes a new instance of the <see cref="T:Terminal.Window"/> class with an optioanl title
  687. /// </summary>
  688. /// <param name="frame">Frame.</param>
  689. /// <param name="title">Title.</param>
  690. public Window (Rect frame, string title = null) : base (frame)
  691. {
  692. this.Title = title;
  693. frame.Inflate (-1, -1);
  694. contentView = new ContentView (frame);
  695. base.Add (contentView);
  696. }
  697. /// <summary>
  698. /// Enumerates the various views in the ContentView.
  699. /// </summary>
  700. /// <returns>The enumerator.</returns>
  701. public new IEnumerator GetEnumerator ()
  702. {
  703. return contentView.GetEnumerator ();
  704. }
  705. void DrawFrame ()
  706. {
  707. DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), true);
  708. }
  709. /// <summary>
  710. /// Add the specified view to the ContentView.
  711. /// </summary>
  712. /// <param name="view">View to add to the window.</param>
  713. public override void Add (View view)
  714. {
  715. contentView.Add (view);
  716. }
  717. public override void Redraw (Rect bounds)
  718. {
  719. if (!NeedDisplay.IsEmpty) {
  720. Driver.SetAttribute (Colors.Base.Normal);
  721. DrawFrame ();
  722. if (HasFocus)
  723. Driver.SetAttribute (Colors.Dialog.Normal);
  724. var width = Frame.Width;
  725. if (Title != null && width > 4) {
  726. Move (1, 0);
  727. Driver.AddCh (' ');
  728. var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
  729. Driver.AddStr (str);
  730. Driver.AddCh (' ');
  731. }
  732. Driver.SetAttribute (Colors.Dialog.Normal);
  733. }
  734. contentView.Redraw (contentView.Bounds);
  735. ClearNeedsDisplay ();
  736. }
  737. }
  738. /// <summary>
  739. /// The application driver for gui.cs
  740. /// </summary>
  741. /// <remarks>
  742. /// <para>
  743. /// You can hook up to the Iteration event to have your method
  744. /// invoked on each iteration of the mainloop.
  745. /// </para>
  746. /// <para>
  747. /// Creates a mainloop to process input events, handle timers and
  748. /// other sources of data. It is accessible via the MainLoop property.
  749. /// </para>
  750. /// <para>
  751. /// When invoked sets the SynchronizationContext to one that is tied
  752. /// to the mainloop, allowing user code to use async/await.
  753. /// </para>
  754. /// </remarks>
  755. public class Application {
  756. public static ConsoleDriver Driver = new CursesDriver ();
  757. public static Toplevel Top { get; private set; }
  758. public static View Current { get; private set; }
  759. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  760. static Stack<View> toplevels = new Stack<View> ();
  761. /// <summary>
  762. /// This event is raised on each iteration of the
  763. /// main loop.
  764. /// </summary>
  765. /// <remarks>
  766. /// See also <see cref="Timeout"/>
  767. /// </remarks>
  768. static public event EventHandler Iteration;
  769. /// <summary>
  770. /// Returns a rectangle that is centered in the screen for the provided size.
  771. /// </summary>
  772. /// <returns>The centered rect.</returns>
  773. /// <param name="size">Size for the rectangle.</param>
  774. public static Rect MakeCenteredRect (Size size)
  775. {
  776. return new Rect (new Point ((Driver.Cols - size.Width) / 2, (Driver.Rows - size.Height) / 2), size);
  777. }
  778. //
  779. // provides the sync context set while executing code in gui.cs, to let
  780. // users use async/await on their code
  781. //
  782. class MainLoopSyncContext : SynchronizationContext {
  783. Mono.Terminal.MainLoop mainLoop;
  784. public MainLoopSyncContext (Mono.Terminal.MainLoop mainLoop)
  785. {
  786. this.mainLoop = mainLoop;
  787. }
  788. public override SynchronizationContext CreateCopy ()
  789. {
  790. return new MainLoopSyncContext (MainLoop);
  791. }
  792. public override void Post (SendOrPostCallback d, object state)
  793. {
  794. mainLoop.AddIdle (() => {
  795. d (state);
  796. return false;
  797. });
  798. }
  799. public override void Send (SendOrPostCallback d, object state)
  800. {
  801. mainLoop.Invoke (() => {
  802. d (state);
  803. });
  804. }
  805. }
  806. /// <summary>
  807. /// Initializes the Application
  808. /// </summary>
  809. public static void Init ()
  810. {
  811. if (Top != null)
  812. return;
  813. Driver.Init (TerminalResized);
  814. MainLoop = new Mono.Terminal.MainLoop ();
  815. SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext (MainLoop));
  816. Top = Toplevel.Create ();
  817. Current = Top;
  818. }
  819. public class RunState : IDisposable {
  820. internal RunState (Toplevel view)
  821. {
  822. Toplevel = view;
  823. }
  824. internal Toplevel Toplevel;
  825. public void Dispose ()
  826. {
  827. Dispose (true);
  828. GC.SuppressFinalize (this);
  829. }
  830. public virtual void Dispose (bool disposing)
  831. {
  832. if (Toplevel != null) {
  833. Application.End (Toplevel);
  834. Toplevel = null;
  835. }
  836. }
  837. }
  838. static void ProcessKeyEvent (KeyEvent ke)
  839. {
  840. if (Current.ProcessHotKey (ke))
  841. return;
  842. if (Current.ProcessKey (ke))
  843. return;
  844. // Process the key normally
  845. if (Current.ProcessColdKey (ke))
  846. return;
  847. }
  848. static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
  849. {
  850. var startFrame = start.Frame;
  851. if (!startFrame.Contains (x, y)) {
  852. resx = 0;
  853. resy = 0;
  854. return null;
  855. }
  856. if (start.Subviews != null){
  857. int count = start.Subviews.Count;
  858. if (count > 0) {
  859. var rx = x - startFrame.X;
  860. var ry = y - startFrame.Y;
  861. for (int i = count - 1; i >= 0; i--) {
  862. View v = start.Subviews [i];
  863. if (v.Frame.Contains (rx, ry)) {
  864. var deep = FindDeepestView (v, rx, ry, out resx, out resy);
  865. if (deep == null)
  866. return v;
  867. return deep;
  868. }
  869. }
  870. }
  871. }
  872. resx = x-startFrame.X;
  873. resy = y-startFrame.Y;
  874. return start;
  875. }
  876. /// <summary>
  877. /// Merely a debugging aid to see the raw mouse events
  878. /// </summary>
  879. static public Action<MouseEvent> RootMouseEvent;
  880. static void ProcessMouseEvent (MouseEvent me)
  881. {
  882. RootMouseEvent?.Invoke (me);
  883. int rx, ry;
  884. var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
  885. if (view != null) {
  886. var nme = new MouseEvent () {
  887. X = rx,
  888. Y = ry,
  889. Flags = me.Flags
  890. };
  891. // Should we bubbled up the event, if it is not handled?
  892. view.MouseEvent (nme);
  893. }
  894. }
  895. static public RunState Begin (Toplevel toplevel)
  896. {
  897. if (toplevel == null)
  898. throw new ArgumentNullException (nameof (toplevel));
  899. var rs = new RunState (toplevel);
  900. Init ();
  901. toplevels.Push (toplevel);
  902. Current = toplevel;
  903. Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
  904. toplevel.LayoutSubviews ();
  905. toplevel.FocusFirst ();
  906. Redraw (toplevel);
  907. toplevel.PositionCursor ();
  908. Driver.Refresh ();
  909. return rs;
  910. }
  911. static public void End (RunState rs)
  912. {
  913. if (rs == null)
  914. throw new ArgumentNullException (nameof (rs));
  915. rs.Dispose ();
  916. }
  917. static void Shutdown ()
  918. {
  919. Driver.End ();
  920. }
  921. static void Redraw (View view)
  922. {
  923. view.Redraw (view.Bounds);
  924. Driver.Refresh ();
  925. }
  926. static void Refresh (View view)
  927. {
  928. view.Redraw (view.Bounds);
  929. Driver.Refresh ();
  930. }
  931. /// <summary>
  932. /// Triggers a refresh of the entire display.
  933. /// </summary>
  934. public static void Refresh ()
  935. {
  936. Driver.RedrawTop ();
  937. View last = null;
  938. foreach (var v in toplevels.Reverse ()) {
  939. v.SetNeedsDisplay ();
  940. v.Redraw (v.Bounds);
  941. last = v;
  942. }
  943. last?.PositionCursor ();
  944. Driver.Refresh ();
  945. }
  946. internal static void End (View view)
  947. {
  948. if (toplevels.Peek () != view)
  949. throw new ArgumentException ("The view that you end with must be balanced");
  950. toplevels.Pop ();
  951. if (toplevels.Count == 0)
  952. Shutdown ();
  953. else {
  954. Current = toplevels.Peek ();
  955. Refresh ();
  956. }
  957. }
  958. /// <summary>
  959. /// Runs the main loop for the created dialog
  960. /// </summary>
  961. /// <remarks>
  962. /// Use the wait parameter to control whether this is a
  963. /// blocking or non-blocking call.
  964. /// </remarks>
  965. public static void RunLoop (RunState state, bool wait = true)
  966. {
  967. if (state == null)
  968. throw new ArgumentNullException (nameof (state));
  969. if (state.Toplevel == null)
  970. throw new ObjectDisposedException ("state");
  971. for (state.Toplevel.Running = true; state.Toplevel.Running;) {
  972. if (MainLoop.EventsPending (wait)) {
  973. MainLoop.MainIteration ();
  974. if (Iteration != null)
  975. Iteration (null, EventArgs.Empty);
  976. } else if (wait == false)
  977. return;
  978. if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
  979. state.Toplevel.Redraw (state.Toplevel.Bounds);
  980. state.Toplevel.PositionCursor ();
  981. Driver.Refresh ();
  982. }
  983. }
  984. }
  985. public static void Run ()
  986. {
  987. Run (Top);
  988. }
  989. /// <summary>
  990. /// Runs the main loop on the given container.
  991. /// </summary>
  992. /// <remarks>
  993. /// <para>
  994. /// This method is used to start processing events
  995. /// for the main application, but it is also used to
  996. /// run modal dialog boxes.
  997. /// </para>
  998. /// <para>
  999. /// To make a toplevel stop execution, set the "Running"
  1000. /// property to false.
  1001. /// </para>
  1002. /// </remarks>
  1003. public static void Run (Toplevel view)
  1004. {
  1005. var runToken = Begin (view);
  1006. RunLoop (runToken);
  1007. End (runToken);
  1008. }
  1009. static void TerminalResized ()
  1010. {
  1011. foreach (var t in toplevels) {
  1012. t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
  1013. }
  1014. }
  1015. }
  1016. }