Core.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. //
  2. //
  3. // Pending:
  4. // - Check for NeedDisplay on the hierarchy and repaint
  5. // - Layout support
  6. // - "Colors" type or "Attributes" type?
  7. // - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors?
  8. //
  9. // Optimziations
  10. // - Add rendering limitation to the exposed area
  11. using System;
  12. using System.Collections;
  13. using System.Collections.Generic;
  14. namespace Terminal {
  15. public class Responder {
  16. public virtual bool CanFocus { get; set; }
  17. public virtual bool HasFocus { get; internal set; }
  18. // Key handling
  19. /// <summary>
  20. /// This method can be overwritten by view that
  21. /// want to provide accelerator functionality
  22. /// (Alt-key for example).
  23. /// </summary>
  24. /// <remarks>
  25. /// <para>
  26. /// Before keys are sent to the subview on the
  27. /// current view, all the views are
  28. /// processed and the key is passed to the widgets
  29. /// to allow some of them to process the keystroke
  30. /// as a hot-key. </para>
  31. /// <para>
  32. /// For example, if you implement a button that
  33. /// has a hotkey ok "o", you would catch the
  34. /// combination Alt-o here. If the event is
  35. /// caught, you must return true to stop the
  36. /// keystroke from being dispatched to other
  37. /// views.
  38. /// </para>
  39. /// </remarks>
  40. public virtual bool ProcessHotKey (KeyEvent kb)
  41. {
  42. return false;
  43. }
  44. /// <summary>
  45. /// If the view is focused, gives the view a
  46. /// chance to process the keystroke.
  47. /// </summary>
  48. /// <remarks>
  49. /// <para>
  50. /// Views can override this method if they are
  51. /// interested in processing the given keystroke.
  52. /// If they consume the keystroke, they must
  53. /// return true to stop the keystroke from being
  54. /// processed by other widgets or consumed by the
  55. /// widget engine. If they return false, the
  56. /// keystroke will be passed using the ProcessColdKey
  57. /// method to other views to process.
  58. /// </para>
  59. /// </remarks>
  60. public virtual bool ProcessKey (KeyEvent kb)
  61. {
  62. return false;
  63. }
  64. /// <summary>
  65. /// This method can be overwritten by views that
  66. /// want to provide accelerator functionality
  67. /// (Alt-key for example), but without
  68. /// interefering with normal ProcessKey behavior.
  69. /// </summary>
  70. /// <remarks>
  71. /// <para>
  72. /// After keys are sent to the subviews on the
  73. /// current view, all the view are
  74. /// processed and the key is passed to the views
  75. /// to allow some of them to process the keystroke
  76. /// as a cold-key. </para>
  77. /// <para>
  78. /// This functionality is used, for example, by
  79. /// default buttons to act on the enter key.
  80. /// Processing this as a hot-key would prevent
  81. /// non-default buttons from consuming the enter
  82. /// keypress when they have the focus.
  83. /// </para>
  84. /// </remarks>
  85. public virtual bool ProcessColdKey (KeyEvent kb)
  86. {
  87. return false;
  88. }
  89. // Mouse events
  90. public virtual void MouseEvent (Event.Mouse me) { }
  91. }
  92. public class View : Responder, IEnumerable {
  93. string id = "";
  94. View container = null;
  95. View focused = null;
  96. public static ConsoleDriver Driver = Application.Driver;
  97. public static IList<View> empty = new List<View> (0).AsReadOnly ();
  98. List<View> subviews;
  99. public IList<View> Subviews => subviews == null ? empty : subviews.AsReadOnly ();
  100. internal Rect NeedDisplay { get; private set; } = Rect.Empty;
  101. // The frame for the object
  102. Rect frame;
  103. public string Id {
  104. get => id;
  105. set {
  106. id = value;
  107. }
  108. }
  109. // The frame for this view
  110. public Rect Frame {
  111. get => frame;
  112. set {
  113. if (SuperView != null) {
  114. SuperView.SetNeedsDisplay (frame);
  115. SuperView.SetNeedsDisplay (value);
  116. }
  117. frame = value;
  118. SetNeedsDisplay (frame);
  119. }
  120. }
  121. public IEnumerator GetEnumerator ()
  122. {
  123. foreach (var v in subviews)
  124. yield return v;
  125. }
  126. public Rect Bounds {
  127. get => new Rect (Point.Empty, Frame.Size);
  128. set {
  129. Frame = new Rect (frame.Location, value.Size);
  130. }
  131. }
  132. public View SuperView => container;
  133. public View (Rect frame)
  134. {
  135. this.Frame = frame;
  136. CanFocus = false;
  137. }
  138. /// <summary>
  139. /// Invoke to flag that this view needs to be redisplayed, by any code
  140. /// that alters the state of the view.
  141. /// </summary>
  142. public void SetNeedsDisplay ()
  143. {
  144. SetNeedsDisplay (Frame);
  145. }
  146. public void SetNeedsDisplay (Rect region)
  147. {
  148. if (NeedDisplay.IsEmpty)
  149. NeedDisplay = region;
  150. else {
  151. var x = Math.Min (NeedDisplay.X, region.X);
  152. var y = Math.Min (NeedDisplay.Y, region.Y);
  153. var w = Math.Max (NeedDisplay.Width, region.Width);
  154. var h = Math.Max (NeedDisplay.Height, region.Height);
  155. NeedDisplay = new Rect (x, y, w, h);
  156. }
  157. if (container != null)
  158. container.ChildNeedsDisplay ();
  159. if (subviews == null)
  160. return;
  161. foreach (var view in subviews)
  162. if (view.Frame.IntersectsWith (region)) {
  163. view.SetNeedsDisplay (Rect.Intersect (view.Frame, region));
  164. }
  165. }
  166. internal bool childNeedsDisplay;
  167. public void ChildNeedsDisplay ()
  168. {
  169. childNeedsDisplay = true;
  170. if (container != null)
  171. container.ChildNeedsDisplay ();
  172. }
  173. /// <summary>
  174. /// Adds a subview to this view.
  175. /// </summary>
  176. /// <remarks>
  177. /// </remarks>
  178. public virtual void Add (View view)
  179. {
  180. if (view == null)
  181. return;
  182. if (subviews == null)
  183. subviews = new List<View> ();
  184. subviews.Add (view);
  185. view.container = this;
  186. if (view.CanFocus)
  187. CanFocus = true;
  188. SetNeedsDisplay ();
  189. }
  190. public void Add (params View [] views)
  191. {
  192. if (views == null)
  193. return;
  194. foreach (var view in views)
  195. Add (view);
  196. }
  197. /// <summary>
  198. /// Removes all the widgets from this container.
  199. /// </summary>
  200. /// <remarks>
  201. /// </remarks>
  202. public virtual void RemoveAll ()
  203. {
  204. if (subviews == null)
  205. return;
  206. while (subviews.Count > 0) {
  207. var view = subviews [0];
  208. Remove (view);
  209. subviews.RemoveAt (0);
  210. }
  211. }
  212. /// <summary>
  213. /// Removes a widget from this container.
  214. /// </summary>
  215. /// <remarks>
  216. /// </remarks>
  217. public virtual void Remove (View view)
  218. {
  219. if (view == null)
  220. return;
  221. SetNeedsDisplay ();
  222. var touched = view.Frame;
  223. subviews.Remove (view);
  224. view.container = null;
  225. if (subviews.Count < 1)
  226. this.CanFocus = false;
  227. foreach (var v in subviews) {
  228. if (v.Frame.IntersectsWith (touched))
  229. view.SetNeedsDisplay ();
  230. }
  231. }
  232. /// <summary>
  233. /// Clears the view region with the current color.
  234. /// </summary>
  235. /// <remarks>
  236. /// <para>
  237. /// This clears the entire region used by this view.
  238. /// </para>
  239. /// </remarks>
  240. public void Clear ()
  241. {
  242. var h = Frame.Height;
  243. var w = Frame.Width;
  244. for (int line = 0; line < h; line++) {
  245. Move (0, line);
  246. for (int col = 0; col < w; col++)
  247. Driver.AddCh (' ');
  248. }
  249. }
  250. /// <summary>
  251. /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
  252. /// </summary>
  253. /// <param name="col">View-based column.</param>
  254. /// <param name="row">View-based row.</param>
  255. /// <param name="rcol">Absolute column, display relative.</param>
  256. /// <param name="rrow">Absolute row, display relative.</param>
  257. internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  258. {
  259. // Computes the real row, col relative to the screen.
  260. rrow = row + frame.Y;
  261. rcol = col + frame.X;
  262. var ccontainer = container;
  263. while (ccontainer != null) {
  264. rrow += ccontainer.frame.Y;
  265. rcol += ccontainer.frame.X;
  266. ccontainer = ccontainer.container;
  267. }
  268. // The following ensures that the cursor is always in the screen boundaries.
  269. if (clipped) {
  270. rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
  271. rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
  272. }
  273. }
  274. // Converts a rectangle in view coordinates to screen coordinates.
  275. Rect RectToScreen (Rect rect)
  276. {
  277. ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
  278. return new Rect (x, y, rect.Width, rect.Height);
  279. }
  280. // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
  281. Rect ScreenClip (Rect rect)
  282. {
  283. var x = rect.X < 0 ? 0 : rect.X;
  284. var y = rect.Y < 0 ? 0 : rect.Y;
  285. var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
  286. var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
  287. return new Rect (x, y, w, h);
  288. }
  289. /// <summary>
  290. /// Draws a frame in the current view, clipped by the boundary of this view
  291. /// </summary>
  292. /// <param name="rect">Rectangular region for the frame to be drawn.</param>
  293. /// <param name="fill">If set to <c>true</c> it fill will the contents.</param>
  294. public void DrawFrame (Rect rect, bool fill = false)
  295. {
  296. var scrRect = RectToScreen (rect);
  297. var savedClip = Driver.Clip;
  298. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  299. Driver.DrawFrame (scrRect, fill);
  300. Driver.Clip = savedClip;
  301. }
  302. /// <summary>
  303. /// Utility function to draw strings that contain a hotkey
  304. /// </summary>
  305. /// <param name="s">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  306. /// <param name="hotColor">Hot color.</param>
  307. /// <param name="normalColor">Normal color.</param>
  308. public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
  309. {
  310. Driver.SetAttribute (normalColor);
  311. foreach (var c in text) {
  312. if (c == '_') {
  313. Driver.SetAttribute (hotColor);
  314. continue;
  315. }
  316. Driver.AddCh (c);
  317. Driver.SetAttribute (normalColor);
  318. }
  319. }
  320. /// <summary>
  321. /// This moves the cursor to the specified column and row in the view.
  322. /// </summary>
  323. /// <returns>The move.</returns>
  324. /// <param name="col">Col.</param>
  325. /// <param name="row">Row.</param>
  326. public void Move (int col, int row)
  327. {
  328. ViewToScreen (col, row, out var rcol, out var rrow);
  329. Driver.Move (rcol, rrow);
  330. }
  331. /// <summary>
  332. /// Positions the cursor in the right position based on the currently focused view in the chain.
  333. /// </summary>
  334. public virtual void PositionCursor ()
  335. {
  336. if (focused != null)
  337. focused.PositionCursor ();
  338. else
  339. Move (frame.X, frame.Y);
  340. }
  341. public override bool HasFocus {
  342. get {
  343. return base.HasFocus;
  344. }
  345. internal set {
  346. if (base.HasFocus != value)
  347. SetNeedsDisplay ();
  348. base.HasFocus = value;
  349. }
  350. }
  351. /// <summary>
  352. /// Returns the currently focused view inside this view, or null if nothing is focused.
  353. /// </summary>
  354. /// <value>The focused.</value>
  355. public View Focused => focused;
  356. public View MostFocused {
  357. get {
  358. if (Focused == null)
  359. return null;
  360. var most = Focused.MostFocused;
  361. if (most != null)
  362. return most;
  363. return Focused;
  364. }
  365. }
  366. /// <summary>
  367. /// Displays the specified character in the specified column and row.
  368. /// </summary>
  369. /// <param name="col">Col.</param>
  370. /// <param name="row">Row.</param>
  371. /// <param name="ch">Ch.</param>
  372. public void AddCh (int col, int row, int ch)
  373. {
  374. if (row < 0 || col < 0)
  375. return;
  376. if (row > frame.Height - 1 || col > frame.Width - 1)
  377. return;
  378. Move (col, row);
  379. Driver.AddCh (ch);
  380. }
  381. /// <summary>
  382. /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
  383. /// </summary>
  384. /// <remarks>
  385. /// The region argument is relative to the view itself.
  386. /// </remarks>
  387. public virtual void Redraw (Rect region)
  388. {
  389. var clipRect = new Rect (Point.Empty, frame.Size);
  390. if (subviews != null) {
  391. foreach (var view in subviews) {
  392. if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) {
  393. if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
  394. // TODO: optimize this by computing the intersection of region and view.Bounds
  395. view.Redraw (view.Bounds);
  396. }
  397. view.NeedDisplay = Rect.Empty;
  398. view.childNeedsDisplay = false;
  399. }
  400. }
  401. }
  402. NeedDisplay = Rect.Empty;
  403. childNeedsDisplay = false;
  404. }
  405. /// <summary>
  406. /// Focuses the specified sub-view.
  407. /// </summary>
  408. /// <param name="view">View.</param>
  409. public void SetFocus (View view)
  410. {
  411. if (view == null)
  412. return;
  413. //Console.WriteLine ($"Request to focus {view}");
  414. if (!view.CanFocus)
  415. return;
  416. if (focused == view)
  417. return;
  418. // Make sure that this view is a subview
  419. View c;
  420. for (c = view.container; c != null; c = c.container)
  421. if (c == this)
  422. break;
  423. if (c == null)
  424. throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
  425. if (focused != null)
  426. focused.HasFocus = false;
  427. focused = view;
  428. focused.HasFocus = true;
  429. focused.EnsureFocus ();
  430. }
  431. public override bool ProcessKey (KeyEvent kb)
  432. {
  433. if (Focused?.ProcessKey (kb) == true)
  434. return true;
  435. return false;
  436. }
  437. public override bool ProcessHotKey (KeyEvent kb)
  438. {
  439. if (subviews == null || subviews.Count == 0)
  440. return false;
  441. foreach (var view in subviews)
  442. if (view.ProcessHotKey (kb))
  443. return true;
  444. return false;
  445. }
  446. public override bool ProcessColdKey (KeyEvent kb)
  447. {
  448. if (subviews == null || subviews.Count == 0)
  449. return false;
  450. foreach (var view in subviews)
  451. if (view.ProcessHotKey (kb))
  452. return true;
  453. return false;
  454. }
  455. /// <summary>
  456. /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
  457. /// </summary>
  458. public void EnsureFocus ()
  459. {
  460. if (focused == null)
  461. FocusFirst ();
  462. }
  463. /// <summary>
  464. /// Focuses the first focusable subview if one exists.
  465. /// </summary>
  466. public void FocusFirst ()
  467. {
  468. if (subviews == null) {
  469. SuperView.SetFocus (this);
  470. return;
  471. }
  472. foreach (var view in subviews) {
  473. if (view.CanFocus) {
  474. SetFocus (view);
  475. return;
  476. }
  477. }
  478. }
  479. /// <summary>
  480. /// Focuses the last focusable subview if one exists.
  481. /// </summary>
  482. public void FocusLast ()
  483. {
  484. if (subviews == null)
  485. return;
  486. for (int i = subviews.Count; i > 0;) {
  487. i--;
  488. View v = subviews [i];
  489. if (v.CanFocus) {
  490. SetFocus (v);
  491. return;
  492. }
  493. }
  494. }
  495. /// <summary>
  496. /// Focuses the previous view.
  497. /// </summary>
  498. /// <returns><c>true</c>, if previous was focused, <c>false</c> otherwise.</returns>
  499. public bool FocusPrev ()
  500. {
  501. if (focused == null) {
  502. FocusLast ();
  503. return true;
  504. }
  505. int focused_idx = -1;
  506. for (int i = subviews.Count; i > 0;) {
  507. i--;
  508. View w = subviews [i];
  509. if (w.HasFocus) {
  510. if (w.FocusPrev ())
  511. return true;
  512. focused_idx = i;
  513. continue;
  514. }
  515. if (w.CanFocus && focused_idx != -1) {
  516. focused.HasFocus = false;
  517. if (w.CanFocus)
  518. w.FocusLast ();
  519. SetFocus (w);
  520. return true;
  521. }
  522. }
  523. if (focused != null) {
  524. focused.HasFocus = false;
  525. focused = null;
  526. }
  527. return false;
  528. }
  529. /// <summary>
  530. /// Focuses the next view.
  531. /// </summary>
  532. /// <returns><c>true</c>, if next was focused, <c>false</c> otherwise.</returns>
  533. public bool FocusNext ()
  534. {
  535. if (subviews == null || subviews.Count == 0)
  536. return false;
  537. if (focused == null) {
  538. FocusFirst ();
  539. return focused != null;
  540. }
  541. int n = subviews.Count;
  542. int focused_idx = -1;
  543. for (int i = 0; i < n; i++) {
  544. View w = subviews [i];
  545. if (w.HasFocus) {
  546. if (w.FocusNext ())
  547. return true;
  548. focused_idx = i;
  549. continue;
  550. }
  551. if (w.CanFocus && focused_idx != -1) {
  552. focused.HasFocus = false;
  553. if (w != null && w.CanFocus)
  554. w.FocusFirst ();
  555. SetFocus (w);
  556. return true;
  557. }
  558. }
  559. if (focused != null) {
  560. focused.HasFocus = false;
  561. focused = null;
  562. }
  563. return false;
  564. }
  565. public virtual void LayoutSubviews ()
  566. {
  567. }
  568. public override string ToString ()
  569. {
  570. return $"{GetType ().Name}({id})({Frame})";
  571. }
  572. }
  573. /// <summary>
  574. /// Toplevel views can be modally executed.
  575. /// </summary>
  576. public class Toplevel : View {
  577. public bool Running;
  578. public Toplevel (Rect frame) : base (frame)
  579. {
  580. }
  581. public static Toplevel Create ()
  582. {
  583. return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
  584. }
  585. public override bool CanFocus {
  586. get => true;
  587. }
  588. public override bool ProcessKey (KeyEvent kb)
  589. {
  590. if (ProcessHotKey (kb))
  591. return true;
  592. if (base.ProcessKey (kb))
  593. return true;
  594. // Process the key normally
  595. if (ProcessColdKey (kb))
  596. return true;
  597. switch (kb.Key) {
  598. case Key.ControlC:
  599. // TODO: stop current execution of this container
  600. break;
  601. case Key.ControlZ:
  602. // TODO: should suspend
  603. // console_csharp_send_sigtstp ();
  604. break;
  605. case Key.Tab:
  606. var old = Focused;
  607. if (!FocusNext ())
  608. FocusNext ();
  609. if (old != Focused) {
  610. old?.SetNeedsDisplay ();
  611. Focused?.SetNeedsDisplay ();
  612. }
  613. return true;
  614. case Key.BackTab:
  615. old = Focused;
  616. if (!FocusPrev ())
  617. FocusPrev ();
  618. if (old != Focused) {
  619. old?.SetNeedsDisplay ();
  620. Focused?.SetNeedsDisplay ();
  621. }
  622. return true;
  623. case Key.ControlL:
  624. SetNeedsDisplay();
  625. return true;
  626. }
  627. return false;
  628. }
  629. #if false
  630. public override void Redraw ()
  631. {
  632. base.Redraw ();
  633. for (int i = 0; i < Driver.Cols; i++) {
  634. Driver.Move (0, i);
  635. Driver.AddStr ("Line: " + i);
  636. }
  637. }
  638. #endif
  639. }
  640. /// <summary>
  641. /// A toplevel view that draws a frame around its region
  642. /// </summary>
  643. public class Window : Toplevel, IEnumerable {
  644. View contentView;
  645. string title;
  646. public string Title {
  647. get => title;
  648. set {
  649. title = value;
  650. SetNeedsDisplay ();
  651. }
  652. }
  653. class ContentView : View {
  654. public ContentView (Rect frame) : base (frame) { }
  655. }
  656. public Window (Rect frame, string title = null) : base (frame)
  657. {
  658. this.Title = title;
  659. frame.Inflate (-1, -1);
  660. contentView = new ContentView (frame);
  661. base.Add (contentView);
  662. }
  663. public new IEnumerator GetEnumerator ()
  664. {
  665. return contentView.GetEnumerator ();
  666. }
  667. void DrawFrame ()
  668. {
  669. DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), true);
  670. }
  671. public override void Add (View view)
  672. {
  673. contentView.Add (view);
  674. }
  675. public override void Redraw (Rect bounds)
  676. {
  677. if (!NeedDisplay.IsEmpty) {
  678. Driver.SetAttribute (Colors.Base.Normal);
  679. DrawFrame ();
  680. if (HasFocus)
  681. Driver.SetAttribute (Colors.Dialog.Normal);
  682. var width = Frame.Width;
  683. if (Title != null && width > 4) {
  684. Move (1, 0);
  685. Driver.AddCh (' ');
  686. var str = Title.Length > width ? Title.Substring (0, width - 4) : Title;
  687. Driver.AddStr (str);
  688. Driver.AddCh (' ');
  689. }
  690. Driver.SetAttribute (Colors.Dialog.Normal);
  691. }
  692. contentView.Redraw (contentView.Bounds);
  693. }
  694. }
  695. public class Application {
  696. public static ConsoleDriver Driver = new CursesDriver ();
  697. public static Toplevel Top { get; private set; }
  698. public static Mono.Terminal.MainLoop MainLoop { get; private set; }
  699. static Stack<View> toplevels = new Stack<View> ();
  700. static Responder focus;
  701. /// <summary>
  702. /// This event is raised on each iteration of the
  703. /// main loop.
  704. /// </summary>
  705. /// <remarks>
  706. /// See also <see cref="Timeout"/>
  707. /// </remarks>
  708. static public event EventHandler Iteration;
  709. public static void MakeFirstResponder (Responder newResponder)
  710. {
  711. if (newResponder == null)
  712. throw new ArgumentNullException ();
  713. throw new NotImplementedException ();
  714. }
  715. /// <summary>
  716. /// Initializes the Application
  717. /// </summary>
  718. public static void Init ()
  719. {
  720. if (Top != null)
  721. return;
  722. Driver.Init (TerminalResized);
  723. MainLoop = new Mono.Terminal.MainLoop ();
  724. Top = Toplevel.Create ();
  725. focus = Top;
  726. }
  727. public class RunState : IDisposable {
  728. internal RunState (Toplevel view)
  729. {
  730. Toplevel = view;
  731. }
  732. internal Toplevel Toplevel;
  733. public void Dispose ()
  734. {
  735. Dispose (true);
  736. GC.SuppressFinalize (this);
  737. }
  738. public virtual void Dispose (bool disposing)
  739. {
  740. if (Toplevel != null) {
  741. Application.End (Toplevel);
  742. Toplevel = null;
  743. }
  744. }
  745. }
  746. static void KeyEvent (Key key)
  747. {
  748. }
  749. static public RunState Begin (Toplevel toplevel)
  750. {
  751. if (toplevel == null)
  752. throw new ArgumentNullException (nameof (toplevel));
  753. var rs = new RunState (toplevel);
  754. Init ();
  755. toplevels.Push (toplevel);
  756. Driver.PrepareToRun (MainLoop, toplevel);
  757. toplevel.LayoutSubviews ();
  758. toplevel.FocusFirst ();
  759. Redraw (toplevel);
  760. toplevel.PositionCursor ();
  761. Driver.Refresh ();
  762. return rs;
  763. }
  764. static public void End (RunState rs)
  765. {
  766. if (rs == null)
  767. throw new ArgumentNullException (nameof (rs));
  768. rs.Dispose ();
  769. }
  770. static void Shutdown ()
  771. {
  772. Driver.End ();
  773. }
  774. static void Redraw (View view)
  775. {
  776. view.Redraw (view.Bounds);
  777. Driver.Refresh ();
  778. }
  779. static void Refresh (View view)
  780. {
  781. view.Redraw (view.Bounds);
  782. Driver.Refresh ();
  783. }
  784. public static void Refresh ()
  785. {
  786. Driver.RedrawTop ();
  787. View last = null;
  788. foreach (var v in toplevels) {
  789. v.Redraw (v.Bounds);
  790. last = v;
  791. }
  792. if (last != null)
  793. last.PositionCursor ();
  794. Driver.Refresh ();
  795. }
  796. internal static void End (View view)
  797. {
  798. if (toplevels.Peek () != view)
  799. throw new ArgumentException ("The view that you end with must be balanced");
  800. toplevels.Pop ();
  801. if (toplevels.Count == 0)
  802. Shutdown ();
  803. else
  804. Refresh ();
  805. }
  806. /// <summary>
  807. /// Runs the main loop for the created dialog
  808. /// </summary>
  809. /// <remarks>
  810. /// Use the wait parameter to control whether this is a
  811. /// blocking or non-blocking call.
  812. /// </remarks>
  813. public static void RunLoop (RunState state, bool wait = true)
  814. {
  815. if (state == null)
  816. throw new ArgumentNullException (nameof (state));
  817. if (state.Toplevel == null)
  818. throw new ObjectDisposedException ("state");
  819. for (state.Toplevel.Running = true; state.Toplevel.Running;) {
  820. if (MainLoop.EventsPending (wait)) {
  821. MainLoop.MainIteration ();
  822. if (Iteration != null)
  823. Iteration (null, EventArgs.Empty);
  824. } else if (wait == false)
  825. return;
  826. if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.childNeedsDisplay) {
  827. state.Toplevel.Redraw (state.Toplevel.Bounds);
  828. state.Toplevel.PositionCursor ();
  829. Driver.Refresh ();
  830. }
  831. }
  832. }
  833. public static void Run ()
  834. {
  835. Run (Top);
  836. }
  837. /// <summary>
  838. /// Runs the main loop on the given container.
  839. /// </summary>
  840. /// <remarks>
  841. /// This method is used to start processing events
  842. /// for the main application, but it is also used to
  843. /// run modal dialog boxes.
  844. /// </remarks>
  845. public static void Run (Toplevel view)
  846. {
  847. var runToken = Begin (view);
  848. RunLoop (runToken);
  849. End (runToken);
  850. }
  851. static void TerminalResized ()
  852. {
  853. foreach (var t in toplevels) {
  854. t.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows);
  855. }
  856. }
  857. }
  858. }