Core.cs 37 KB

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