View.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311
  1. //
  2. // Authors:
  3. // Miguel de Icaza ([email protected])
  4. //
  5. // Pending:
  6. // - Check for NeedDisplay on the hierarchy and repaint
  7. // - Layout support
  8. // - "Colors" type or "Attributes" type?
  9. // - What to surface as "BackgroundCOlor" when clearing a window, an attribute or colors?
  10. //
  11. // Optimziations
  12. // - Add rendering limitation to the exposed area
  13. using System;
  14. using System.Collections;
  15. using System.Collections.Generic;
  16. using System.Linq;
  17. using NStack;
  18. namespace Terminal.Gui {
  19. /// <summary>
  20. /// Determines the LayoutStyle for a view, if Absolute, during LayoutSubviews, the
  21. /// value from the Frame will be used, if the value is Computer, then the Frame
  22. /// will be updated from the X, Y Pos objects and the Width and Height Dim objects.
  23. /// </summary>
  24. public enum LayoutStyle {
  25. /// <summary>
  26. /// The position and size of the view are based on the Frame value.
  27. /// </summary>
  28. Absolute,
  29. /// <summary>
  30. /// The position and size of the view will be computed based on the
  31. /// X, Y, Width and Height properties and set on the Frame.
  32. /// </summary>
  33. Computed
  34. }
  35. /// <summary>
  36. /// 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.
  37. /// </summary>
  38. /// <remarks>
  39. /// <para>
  40. /// The View defines the base functionality for user interface elements in Terminal/gui.cs. Views
  41. /// can contain one or more subviews, can respond to user input and render themselves on the screen.
  42. /// </para>
  43. /// <para>
  44. /// Views can either be created with an absolute position, by calling the constructor that takes a
  45. /// Rect parameter to specify the absolute position and size (the Frame of the View) or by setting the
  46. /// X, Y, Width and Height properties on the view. Both approaches use coordinates that are relative
  47. /// to the container they are being added to.
  48. /// </para>
  49. /// <para>
  50. /// When you do not specify a Rect frame you can use the more flexible
  51. /// Dim and Pos objects that can dynamically update the position of a view.
  52. /// The X and Y properties are of type <see cref="Pos"/>
  53. /// and you can use either absolute positions, percentages or anchor
  54. /// points. The Width and Height properties are of type
  55. /// <see cref="Dim"/> and can use absolute position,
  56. /// percentages and anchors. These are useful as they will take
  57. /// care of repositioning your views if your view's frames are resized
  58. /// or if the terminal size changes.
  59. /// </para>
  60. /// <para>
  61. /// When you specify the Rect parameter to a view, you are setting the LayoutStyle to Absolute, and the
  62. /// view will always stay in the position that you placed it. To change the position change the
  63. /// Frame property to the new position.
  64. /// </para>
  65. /// <para>
  66. /// Subviews can be added to a View by calling the Add method. The container of a view is the
  67. /// Superview.
  68. /// </para>
  69. /// <para>
  70. /// Developers can call the SetNeedsDisplay method on the view to flag a region or the entire view
  71. /// as requiring to be redrawn.
  72. /// </para>
  73. /// <para>
  74. /// Views have a ColorScheme property that defines the default colors that subviews
  75. /// should use for rendering. This ensures that the views fit in the context where
  76. /// they are being used, and allows for themes to be plugged in. For example, the
  77. /// default colors for windows and toplevels uses a blue background, while it uses
  78. /// a white background for dialog boxes and a red background for errors.
  79. /// </para>
  80. /// <para>
  81. /// If a ColorScheme is not set on a view, the result of the ColorScheme is the
  82. /// value of the SuperView and the value might only be valid once a view has been
  83. /// added to a SuperView, so your subclasses should not rely on ColorScheme being
  84. /// set at construction time.
  85. /// </para>
  86. /// <para>
  87. /// Using ColorSchemes has the advantage that your application will work both
  88. /// in color as well as black and white displays.
  89. /// </para>
  90. /// <para>
  91. /// Views that are focusable should implement the PositionCursor to make sure that
  92. /// the cursor is placed in a location that makes sense. Unix terminals do not have
  93. /// a way of hiding the cursor, so it can be distracting to have the cursor left at
  94. /// the last focused view. So views should make sure that they place the cursor
  95. /// in a visually sensible place.
  96. /// </para>
  97. /// <para>
  98. /// The metnod LayoutSubviews is invoked when the size or layout of a view has
  99. /// changed. The default processing system will keep the size and dimensions
  100. /// for views that use the LayoutKind.Absolute, and will recompute the
  101. /// frames for the vies that use LayoutKind.Computed.
  102. /// </para>
  103. /// </remarks>
  104. public class View : Responder, IEnumerable {
  105. internal enum Direction {
  106. Forward,
  107. Backward
  108. }
  109. View container = null;
  110. View focused = null;
  111. Direction focusDirection;
  112. /// <summary>
  113. /// Event fired when the view get focus.
  114. /// </summary>
  115. public event EventHandler Enter;
  116. /// <summary>
  117. /// Event fired when the view lost focus.
  118. /// </summary>
  119. public event EventHandler Leave;
  120. /// <summary>
  121. /// Event fired when the view receives the mouse event for the first time.
  122. /// </summary>
  123. public event EventHandler<MouseEvent> MouseEnter;
  124. /// <summary>
  125. /// Event fired when the view loses mouse event for the last time.
  126. /// </summary>
  127. public event EventHandler<MouseEvent> MouseLeave;
  128. internal Direction FocusDirection {
  129. get => SuperView?.FocusDirection ?? focusDirection;
  130. set {
  131. if (SuperView != null)
  132. SuperView.FocusDirection = value;
  133. else
  134. focusDirection = value;
  135. }
  136. }
  137. /// <summary>
  138. /// Points to the current driver in use by the view, it is a convenience property
  139. /// for simplifying the development of new views.
  140. /// </summary>
  141. public static ConsoleDriver Driver { get { return Application.Driver; } }
  142. static IList<View> empty = new List<View> (0).AsReadOnly ();
  143. // This is null, and allocated on demand.
  144. List<View> subviews;
  145. /// <summary>
  146. /// This returns a list of the subviews contained by this view.
  147. /// </summary>
  148. /// <value>The subviews.</value>
  149. public IList<View> Subviews => subviews == null ? empty : subviews.AsReadOnly ();
  150. // Internally, we use InternalSubviews rather than subviews, as we do not expect us
  151. // to make the same mistakes our users make when they poke at the Subviews.
  152. internal IList<View> InternalSubviews => subviews ?? empty;
  153. internal Rect NeedDisplay { get; private set; } = Rect.Empty;
  154. // The frame for the object
  155. Rect frame;
  156. /// <summary>
  157. /// Gets or sets an identifier for the view;
  158. /// </summary>
  159. /// <value>The identifier.</value>
  160. public ustring Id { get; set; } = "";
  161. /// <summary>
  162. /// Returns a value indicating if this View is currently on Top (Active)
  163. /// </summary>
  164. public bool IsCurrentTop {
  165. get {
  166. return Application.Current == this;
  167. }
  168. }
  169. /// <summary>
  170. /// Gets or sets a value indicating whether this <see cref="View"/> want mouse position reports.
  171. /// </summary>
  172. /// <value><c>true</c> if want mouse position reports; otherwise, <c>false</c>.</value>
  173. public virtual bool WantMousePositionReports { get; set; } = false;
  174. /// <summary>
  175. /// Gets or sets a value indicating whether this <see cref="View"/> want continuous button pressed event.
  176. /// </summary>
  177. public virtual bool WantContinuousButtonPressed { get; set; } = false;
  178. /// <summary>
  179. /// Gets or sets the frame for the view.
  180. /// </summary>
  181. /// <value>The frame.</value>
  182. /// <remarks>
  183. /// Altering the Frame of a view will trigger the redrawing of the
  184. /// view as well as the redrawing of the affected regions in the superview.
  185. /// </remarks>
  186. public virtual Rect Frame {
  187. get => frame;
  188. set {
  189. if (SuperView != null) {
  190. SuperView.SetNeedsDisplay (frame);
  191. SuperView.SetNeedsDisplay (value);
  192. }
  193. frame = value;
  194. SetNeedsLayout ();
  195. SetNeedsDisplay (frame);
  196. }
  197. }
  198. /// <summary>
  199. /// Gets an enumerator that enumerates the subviews in this view.
  200. /// </summary>
  201. /// <returns>The enumerator.</returns>
  202. public IEnumerator GetEnumerator ()
  203. {
  204. foreach (var v in InternalSubviews)
  205. yield return v;
  206. }
  207. LayoutStyle layoutStyle;
  208. /// <summary>
  209. /// Controls how the view's Frame is computed during the LayoutSubviews method, if Absolute, then
  210. /// LayoutSubviews does not change the Frame properties, otherwise the Frame is updated from the
  211. /// values in X, Y, Width and Height properties.
  212. /// </summary>
  213. /// <value>The layout style.</value>
  214. public LayoutStyle LayoutStyle {
  215. get => layoutStyle;
  216. set {
  217. layoutStyle = value;
  218. SetNeedsLayout ();
  219. }
  220. }
  221. /// <summary>
  222. /// 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.
  223. /// </summary>
  224. /// <value>The bounds.</value>
  225. public Rect Bounds {
  226. get => new Rect (Point.Empty, Frame.Size);
  227. set {
  228. Frame = new Rect (frame.Location, value.Size);
  229. }
  230. }
  231. Pos x, y;
  232. /// <summary>
  233. /// Gets or sets the X position for the view (the column). This is only used when the LayoutStyle is Computed, if the
  234. /// LayoutStyle is set to Absolute, this value is ignored.
  235. /// </summary>
  236. /// <value>The X Position.</value>
  237. public Pos X {
  238. get => x;
  239. set {
  240. x = value;
  241. SetNeedsLayout ();
  242. }
  243. }
  244. /// <summary>
  245. /// Gets or sets the Y position for the view (line). This is only used when the LayoutStyle is Computed, if the
  246. /// LayoutStyle is set to Absolute, this value is ignored.
  247. /// </summary>
  248. /// <value>The y position (line).</value>
  249. public Pos Y {
  250. get => y;
  251. set {
  252. y = value;
  253. SetNeedsLayout ();
  254. }
  255. }
  256. Dim width, height;
  257. /// <summary>
  258. /// Gets or sets the width for the view. This is only used when the LayoutStyle is Computed, if the
  259. /// LayoutStyle is set to Absolute, this value is ignored.
  260. /// </summary>
  261. /// <value>The width.</value>
  262. public Dim Width {
  263. get => width;
  264. set {
  265. width = value;
  266. SetNeedsLayout ();
  267. }
  268. }
  269. /// <summary>
  270. /// Gets or sets the height for the view. This is only used when the LayoutStyle is Computed, if the
  271. /// LayoutStyle is set to Absolute, this value is ignored.
  272. /// </summary>
  273. /// <value>The height.</value>
  274. public Dim Height {
  275. get => height;
  276. set {
  277. height = value;
  278. SetNeedsLayout ();
  279. }
  280. }
  281. /// <summary>
  282. /// Returns the container for this view, or null if this view has not been added to a container.
  283. /// </summary>
  284. /// <value>The super view.</value>
  285. public View SuperView => container;
  286. /// <summary>
  287. /// Initializes a new instance of the <see cref="View"/> class with the absolute
  288. /// dimensions specified in the frame. If you want to have Views that can be positioned with
  289. /// Pos and Dim properties on X, Y, Width and Height, use the empty constructor.
  290. /// </summary>
  291. /// <param name="frame">The region covered by this view.</param>
  292. public View (Rect frame)
  293. {
  294. this.Frame = frame;
  295. CanFocus = false;
  296. LayoutStyle = LayoutStyle.Absolute;
  297. }
  298. /// <summary>
  299. /// Initializes a new instance of the <see cref="View"/> class and sets the
  300. /// view up for Computed layout, which will use the values in X, Y, Width and Height to
  301. /// compute the View's Frame.
  302. /// </summary>
  303. public View ()
  304. {
  305. CanFocus = false;
  306. LayoutStyle = LayoutStyle.Computed;
  307. }
  308. /// <summary>
  309. /// Invoke to flag that this view needs to be redisplayed, by any code
  310. /// that alters the state of the view.
  311. /// </summary>
  312. public void SetNeedsDisplay ()
  313. {
  314. SetNeedsDisplay (Bounds);
  315. }
  316. internal bool layoutNeeded = true;
  317. internal void SetNeedsLayout ()
  318. {
  319. if (layoutNeeded)
  320. return;
  321. layoutNeeded = true;
  322. if (SuperView == null)
  323. return;
  324. SuperView.SetNeedsLayout ();
  325. }
  326. /// <summary>
  327. /// Flags the specified rectangle region on this view as needing to be repainted.
  328. /// </summary>
  329. /// <param name="region">The region that must be flagged for repaint.</param>
  330. public void SetNeedsDisplay (Rect region)
  331. {
  332. if (NeedDisplay == null || NeedDisplay.IsEmpty)
  333. NeedDisplay = region;
  334. else {
  335. var x = Math.Min (NeedDisplay.X, region.X);
  336. var y = Math.Min (NeedDisplay.Y, region.Y);
  337. var w = Math.Max (NeedDisplay.Width, region.Width);
  338. var h = Math.Max (NeedDisplay.Height, region.Height);
  339. NeedDisplay = new Rect (x, y, w, h);
  340. }
  341. if (container != null)
  342. container.ChildNeedsDisplay ();
  343. if (subviews == null)
  344. return;
  345. foreach (var view in subviews)
  346. if (view.Frame.IntersectsWith (region)) {
  347. var childRegion = Rect.Intersect (view.Frame, region);
  348. childRegion.X -= view.Frame.X;
  349. childRegion.Y -= view.Frame.Y;
  350. view.SetNeedsDisplay (childRegion);
  351. }
  352. }
  353. internal bool childNeedsDisplay;
  354. /// <summary>
  355. /// Flags this view for requiring the children views to be repainted.
  356. /// </summary>
  357. public void ChildNeedsDisplay ()
  358. {
  359. childNeedsDisplay = true;
  360. if (container != null)
  361. container.ChildNeedsDisplay ();
  362. }
  363. /// <summary>
  364. /// Adds a subview to this view.
  365. /// </summary>
  366. /// <remarks>
  367. /// </remarks>
  368. public virtual void Add (View view)
  369. {
  370. if (view == null)
  371. return;
  372. if (subviews == null)
  373. subviews = new List<View> ();
  374. subviews.Add (view);
  375. view.container = this;
  376. if (view.CanFocus)
  377. CanFocus = true;
  378. SetNeedsLayout ();
  379. SetNeedsDisplay ();
  380. }
  381. /// <summary>
  382. /// Adds the specified views to the view.
  383. /// </summary>
  384. /// <param name="views">Array of one or more views (can be optional parameter).</param>
  385. public void Add (params View [] views)
  386. {
  387. if (views == null)
  388. return;
  389. foreach (var view in views)
  390. Add (view);
  391. }
  392. /// <summary>
  393. /// Removes all the widgets from this container.
  394. /// </summary>
  395. /// <remarks>
  396. /// </remarks>
  397. public virtual void RemoveAll ()
  398. {
  399. if (subviews == null)
  400. return;
  401. while (subviews.Count > 0) {
  402. Remove (subviews [0]);
  403. }
  404. }
  405. /// <summary>
  406. /// Removes a widget from this container.
  407. /// </summary>
  408. /// <remarks>
  409. /// </remarks>
  410. public virtual void Remove (View view)
  411. {
  412. if (view == null || subviews == null)
  413. return;
  414. SetNeedsLayout ();
  415. SetNeedsDisplay ();
  416. var touched = view.Frame;
  417. subviews.Remove (view);
  418. view.container = null;
  419. if (subviews.Count < 1)
  420. this.CanFocus = false;
  421. foreach (var v in subviews) {
  422. if (v.Frame.IntersectsWith (touched))
  423. view.SetNeedsDisplay ();
  424. }
  425. }
  426. void PerformActionForSubview (View subview, Action<View> action)
  427. {
  428. if (subviews.Contains (subview)) {
  429. action (subview);
  430. }
  431. SetNeedsDisplay ();
  432. subview.SetNeedsDisplay ();
  433. }
  434. /// <summary>
  435. /// Brings the specified subview to the front so it is drawn on top of any other views.
  436. /// </summary>
  437. /// <param name="subview">The subview to send to the front</param>
  438. /// <remarks>
  439. /// <seealso cref="SendSubviewToBack"/>.
  440. /// </remarks>
  441. public void BringSubviewToFront (View subview)
  442. {
  443. PerformActionForSubview (subview, x => {
  444. subviews.Remove (x);
  445. subviews.Add (x);
  446. });
  447. }
  448. /// <summary>
  449. /// Sends the specified subview to the front so it is the first view drawn
  450. /// </summary>
  451. /// <param name="subview">The subview to send to the front</param>
  452. /// <remarks>
  453. /// <seealso cref="BringSubviewToFront(View)"/>.
  454. /// </remarks>
  455. public void SendSubviewToBack (View subview)
  456. {
  457. PerformActionForSubview (subview, x => {
  458. subviews.Remove (x);
  459. subviews.Insert (0, subview);
  460. });
  461. }
  462. /// <summary>
  463. /// Moves the subview backwards in the hierarchy, only one step
  464. /// </summary>
  465. /// <param name="subview">The subview to send backwards</param>
  466. /// <remarks>
  467. /// If you want to send the view all the way to the back use SendSubviewToBack.
  468. /// </remarks>
  469. public void SendSubviewBackwards (View subview)
  470. {
  471. PerformActionForSubview (subview, x => {
  472. var idx = subviews.IndexOf (x);
  473. if (idx > 0) {
  474. subviews.Remove (x);
  475. subviews.Insert (idx - 1, x);
  476. }
  477. });
  478. }
  479. /// <summary>
  480. /// Moves the subview backwards in the hierarchy, only one step
  481. /// </summary>
  482. /// <param name="subview">The subview to send backwards</param>
  483. /// <remarks>
  484. /// If you want to send the view all the way to the back use SendSubviewToBack.
  485. /// </remarks>
  486. public void BringSubviewForward (View subview)
  487. {
  488. PerformActionForSubview (subview, x => {
  489. var idx = subviews.IndexOf (x);
  490. if (idx + 1 < subviews.Count) {
  491. subviews.Remove (x);
  492. subviews.Insert (idx + 1, x);
  493. }
  494. });
  495. }
  496. /// <summary>
  497. /// Clears the view region with the current color.
  498. /// </summary>
  499. /// <remarks>
  500. /// <para>
  501. /// This clears the entire region used by this view.
  502. /// </para>
  503. /// </remarks>
  504. public void Clear ()
  505. {
  506. var h = Frame.Height;
  507. var w = Frame.Width;
  508. for (int line = 0; line < h; line++) {
  509. Move (0, line);
  510. for (int col = 0; col < w; col++)
  511. Driver.AddRune (' ');
  512. }
  513. }
  514. /// <summary>
  515. /// Clears the specified rectangular region with the current color
  516. /// </summary>
  517. public void Clear (Rect r)
  518. {
  519. var h = r.Height;
  520. var w = r.Width;
  521. for (int line = r.Y; line < r.Y + h; line++) {
  522. Driver.Move (r.X, line);
  523. for (int col = 0; col < w; col++)
  524. Driver.AddRune (' ');
  525. }
  526. }
  527. /// <summary>
  528. /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1)
  529. /// </summary>
  530. /// <param name="col">View-based column.</param>
  531. /// <param name="row">View-based row.</param>
  532. /// <param name="rcol">Absolute column, display relative.</param>
  533. /// <param name="rrow">Absolute row, display relative.</param>
  534. /// <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>
  535. internal void ViewToScreen (int col, int row, out int rcol, out int rrow, bool clipped = true)
  536. {
  537. // Computes the real row, col relative to the screen.
  538. rrow = row + frame.Y;
  539. rcol = col + frame.X;
  540. var ccontainer = container;
  541. while (ccontainer != null) {
  542. rrow += ccontainer.frame.Y;
  543. rcol += ccontainer.frame.X;
  544. ccontainer = ccontainer.container;
  545. }
  546. // The following ensures that the cursor is always in the screen boundaries.
  547. if (clipped) {
  548. rrow = Math.Max (0, Math.Min (rrow, Driver.Rows - 1));
  549. rcol = Math.Max (0, Math.Min (rcol, Driver.Cols - 1));
  550. }
  551. }
  552. /// <summary>
  553. /// Converts a point from screen coordinates into the view coordinate space.
  554. /// </summary>
  555. /// <returns>The mapped point.</returns>
  556. /// <param name="x">X screen-coordinate point.</param>
  557. /// <param name="y">Y screen-coordinate point.</param>
  558. public Point ScreenToView (int x, int y)
  559. {
  560. if (SuperView == null) {
  561. return new Point (x - Frame.X, y - frame.Y);
  562. } else {
  563. var parent = SuperView.ScreenToView (x, y);
  564. return new Point (parent.X - frame.X, parent.Y - frame.Y);
  565. }
  566. }
  567. // Converts a rectangle in view coordinates to screen coordinates.
  568. Rect RectToScreen (Rect rect)
  569. {
  570. ViewToScreen (rect.X, rect.Y, out var x, out var y, clipped: false);
  571. return new Rect (x, y, rect.Width, rect.Height);
  572. }
  573. // Clips a rectangle in screen coordinates to the dimensions currently available on the screen
  574. Rect ScreenClip (Rect rect)
  575. {
  576. var x = rect.X < 0 ? 0 : rect.X;
  577. var y = rect.Y < 0 ? 0 : rect.Y;
  578. var w = rect.X + rect.Width >= Driver.Cols ? Driver.Cols - rect.X : rect.Width;
  579. var h = rect.Y + rect.Height >= Driver.Rows ? Driver.Rows - rect.Y : rect.Height;
  580. return new Rect (x, y, w, h);
  581. }
  582. /// <summary>
  583. /// Sets the Console driver's clip region to the current View's Bounds.
  584. /// </summary>
  585. /// <returns>The existing driver's Clip region, which can be then set by setting the Driver.Clip property.</returns>
  586. public Rect ClipToBounds ()
  587. {
  588. return SetClip (Bounds);
  589. }
  590. /// <summary>
  591. /// Sets the clipping region to the specified region, the region is view-relative
  592. /// </summary>
  593. /// <returns>The previous clip region.</returns>
  594. /// <param name="rect">Rectangle region to clip into, the region is view-relative.</param>
  595. public Rect SetClip (Rect rect)
  596. {
  597. var bscreen = RectToScreen (rect);
  598. var previous = Driver.Clip;
  599. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  600. return previous;
  601. }
  602. /// <summary>
  603. /// Draws a frame in the current view, clipped by the boundary of this view
  604. /// </summary>
  605. /// <param name="rect">Rectangular region for the frame to be drawn.</param>
  606. /// <param name="padding">The padding to add to the drawn frame.</param>
  607. /// <param name="fill">If set to <c>true</c> it fill will the contents.</param>
  608. public void DrawFrame (Rect rect, int padding = 0, bool fill = false)
  609. {
  610. var scrRect = RectToScreen (rect);
  611. var savedClip = Driver.Clip;
  612. Driver.Clip = ScreenClip (RectToScreen (Bounds));
  613. Driver.DrawFrame (scrRect, padding, fill);
  614. Driver.Clip = savedClip;
  615. }
  616. /// <summary>
  617. /// Utility function to draw strings that contain a hotkey
  618. /// </summary>
  619. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  620. /// <param name="hotColor">Hot color.</param>
  621. /// <param name="normalColor">Normal color.</param>
  622. public void DrawHotString (ustring text, Attribute hotColor, Attribute normalColor)
  623. {
  624. Driver.SetAttribute (normalColor);
  625. foreach (var rune in text) {
  626. if (rune == '_') {
  627. Driver.SetAttribute (hotColor);
  628. continue;
  629. }
  630. Driver.AddRune (rune);
  631. Driver.SetAttribute (normalColor);
  632. }
  633. }
  634. /// <summary>
  635. /// Utility function to draw strings that contains a hotkey using a colorscheme and the "focused" state.
  636. /// </summary>
  637. /// <param name="text">String to display, the underscoore before a letter flags the next letter as the hotkey.</param>
  638. /// <param name="focused">If set to <c>true</c> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
  639. /// <param name="scheme">The color scheme to use.</param>
  640. public void DrawHotString (ustring text, bool focused, ColorScheme scheme)
  641. {
  642. if (focused)
  643. DrawHotString (text, scheme.HotFocus, scheme.Focus);
  644. else
  645. DrawHotString (text, scheme.HotNormal, scheme.Normal);
  646. }
  647. /// <summary>
  648. /// This moves the cursor to the specified column and row in the view.
  649. /// </summary>
  650. /// <returns>The move.</returns>
  651. /// <param name="col">Col.</param>
  652. /// <param name="row">Row.</param>
  653. public void Move (int col, int row)
  654. {
  655. ViewToScreen (col, row, out var rcol, out var rrow);
  656. Driver.Move (rcol, rrow);
  657. }
  658. /// <summary>
  659. /// Positions the cursor in the right position based on the currently focused view in the chain.
  660. /// </summary>
  661. public virtual void PositionCursor ()
  662. {
  663. if (focused != null)
  664. focused.PositionCursor ();
  665. else
  666. Move (frame.X, frame.Y);
  667. }
  668. /// <inheritdoc cref="HasFocus"/>
  669. public override bool HasFocus {
  670. get {
  671. return base.HasFocus;
  672. }
  673. internal set {
  674. if (base.HasFocus != value)
  675. if (value)
  676. OnEnter ();
  677. else
  678. OnLeave ();
  679. SetNeedsDisplay ();
  680. base.HasFocus = value;
  681. // Remove focus down the chain of subviews if focus is removed
  682. if (!value && focused != null) {
  683. focused.OnLeave ();
  684. focused.HasFocus = false;
  685. focused = null;
  686. }
  687. }
  688. }
  689. /// <inheritdoc cref="OnEnter"/>
  690. public override bool OnEnter ()
  691. {
  692. Enter?.Invoke (this, new EventArgs ());
  693. return base.OnEnter ();
  694. }
  695. /// <inheritdoc cref="OnLeave"/>
  696. public override bool OnLeave ()
  697. {
  698. Leave?.Invoke (this, new EventArgs ());
  699. return base.OnLeave ();
  700. }
  701. /// <summary>
  702. /// Returns the currently focused view inside this view, or null if nothing is focused.
  703. /// </summary>
  704. /// <value>The focused.</value>
  705. public View Focused => focused;
  706. /// <summary>
  707. /// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
  708. /// </summary>
  709. /// <value>The most focused.</value>
  710. public View MostFocused {
  711. get {
  712. if (Focused == null)
  713. return null;
  714. var most = Focused.MostFocused;
  715. if (most != null)
  716. return most;
  717. return Focused;
  718. }
  719. }
  720. /// <summary>
  721. /// The color scheme for this view, if it is not defined, it returns the parent's
  722. /// color scheme.
  723. /// </summary>
  724. public ColorScheme ColorScheme {
  725. get {
  726. if (colorScheme == null)
  727. return SuperView?.ColorScheme;
  728. return colorScheme;
  729. }
  730. set {
  731. colorScheme = value;
  732. }
  733. }
  734. ColorScheme colorScheme;
  735. /// <summary>
  736. /// Displays the specified character in the specified column and row.
  737. /// </summary>
  738. /// <param name="col">Col.</param>
  739. /// <param name="row">Row.</param>
  740. /// <param name="ch">Ch.</param>
  741. public void AddRune (int col, int row, Rune ch)
  742. {
  743. if (row < 0 || col < 0)
  744. return;
  745. if (row > frame.Height - 1 || col > frame.Width - 1)
  746. return;
  747. Move (col, row);
  748. Driver.AddRune (ch);
  749. }
  750. /// <summary>
  751. /// Removes the SetNeedsDisplay and the ChildNeedsDisplay setting on this view.
  752. /// </summary>
  753. protected void ClearNeedsDisplay ()
  754. {
  755. NeedDisplay = Rect.Empty;
  756. childNeedsDisplay = false;
  757. }
  758. /// <summary>
  759. /// Performs a redraw of this view and its subviews, only redraws the views that have been flagged for a re-display.
  760. /// </summary>
  761. /// <param name="region">The region to redraw, this is relative to the view itself.</param>
  762. /// <remarks>
  763. /// <para>
  764. /// Views should set the color that they want to use on entry, as otherwise this will inherit
  765. /// the last color that was set globaly on the driver.
  766. /// </para>
  767. /// </remarks>
  768. public virtual void Redraw (Rect region)
  769. {
  770. var clipRect = new Rect (Point.Empty, frame.Size);
  771. if (subviews != null) {
  772. foreach (var view in subviews) {
  773. if (view.NeedDisplay != null && (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay)) {
  774. if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) {
  775. // FIXED: optimize this by computing the intersection of region and view.Bounds
  776. if (view.layoutNeeded)
  777. view.LayoutSubviews ();
  778. Application.CurrentView = view;
  779. view.Redraw (view.Bounds);
  780. }
  781. view.NeedDisplay = Rect.Empty;
  782. view.childNeedsDisplay = false;
  783. }
  784. }
  785. }
  786. ClearNeedsDisplay ();
  787. }
  788. /// <summary>
  789. /// Focuses the specified sub-view.
  790. /// </summary>
  791. /// <param name="view">View.</param>
  792. public void SetFocus (View view)
  793. {
  794. if (view == null)
  795. return;
  796. //Console.WriteLine ($"Request to focus {view}");
  797. if (!view.CanFocus)
  798. return;
  799. if (focused == view)
  800. return;
  801. // Make sure that this view is a subview
  802. View c;
  803. for (c = view.container; c != null; c = c.container)
  804. if (c == this)
  805. break;
  806. if (c == null)
  807. throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
  808. if (focused != null)
  809. focused.HasFocus = false;
  810. focused = view;
  811. focused.HasFocus = true;
  812. focused.EnsureFocus ();
  813. // Send focus upwards
  814. SuperView?.SetFocus (this);
  815. }
  816. /// <summary>
  817. /// Specifies the event arguments for <see cref="KeyEvent"/>
  818. /// </summary>
  819. public class KeyEventEventArgs : EventArgs {
  820. /// <summary>
  821. /// Constructs.
  822. /// </summary>
  823. /// <param name="ke"></param>
  824. public KeyEventEventArgs (KeyEvent ke) => KeyEvent = ke;
  825. /// <summary>
  826. /// The <see cref="KeyEvent"/> for the event.
  827. /// </summary>
  828. public KeyEvent KeyEvent { get; set; }
  829. /// <summary>
  830. /// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
  831. /// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
  832. /// </summary>
  833. public bool Handled { get; set; } = false;
  834. }
  835. /// <summary>
  836. /// Invoked when a character key is pressed and occurs after the key up event.
  837. /// </summary>
  838. public event EventHandler<KeyEventEventArgs> KeyPress;
  839. /// <inheritdoc cref="ProcessKey"/>
  840. public override bool ProcessKey (KeyEvent keyEvent)
  841. {
  842. KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
  843. KeyPress?.Invoke (this, args);
  844. if (args.Handled)
  845. return true;
  846. if (Focused?.ProcessKey (keyEvent) == true)
  847. return true;
  848. return false;
  849. }
  850. /// <inheritdoc cref="ProcessHotKey"/>
  851. public override bool ProcessHotKey (KeyEvent keyEvent)
  852. {
  853. KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
  854. KeyPress?.Invoke (this, args);
  855. if (args.Handled)
  856. return true;
  857. if (subviews == null || subviews.Count == 0)
  858. return false;
  859. foreach (var view in subviews)
  860. if (view.ProcessHotKey (keyEvent))
  861. return true;
  862. return false;
  863. }
  864. /// <inheritdoc cref="ProcessColdKey"/>
  865. public override bool ProcessColdKey (KeyEvent keyEvent)
  866. {
  867. KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
  868. KeyPress?.Invoke (this, args);
  869. if (args.Handled)
  870. return true;
  871. if (subviews == null || subviews.Count == 0)
  872. return false;
  873. foreach (var view in subviews)
  874. if (view.ProcessColdKey (keyEvent))
  875. return true;
  876. return false;
  877. }
  878. /// <summary>
  879. /// Invoked when a key is pressed
  880. /// </summary>
  881. public event EventHandler<KeyEventEventArgs> KeyDown;
  882. /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
  883. public override bool OnKeyDown (KeyEvent keyEvent)
  884. {
  885. KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
  886. KeyDown?.Invoke (this, args);
  887. if (args.Handled)
  888. return true;
  889. if (subviews == null || subviews.Count == 0)
  890. return false;
  891. foreach (var view in subviews)
  892. if (view.OnKeyDown (keyEvent))
  893. return true;
  894. return false;
  895. }
  896. /// <summary>
  897. /// Invoked when a key is released
  898. /// </summary>
  899. public event EventHandler<KeyEventEventArgs> KeyUp;
  900. /// <param name="keyEvent">Contains the details about the key that produced the event.</param>
  901. public override bool OnKeyUp (KeyEvent keyEvent)
  902. {
  903. KeyEventEventArgs args = new KeyEventEventArgs (keyEvent);
  904. KeyUp?.Invoke (this, args);
  905. if (args.Handled)
  906. return true;
  907. if (subviews == null || subviews.Count == 0)
  908. return false;
  909. foreach (var view in subviews)
  910. if (view.OnKeyUp (keyEvent))
  911. return true;
  912. return false;
  913. }
  914. /// <summary>
  915. /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
  916. /// </summary>
  917. public void EnsureFocus ()
  918. {
  919. if (focused == null)
  920. if (FocusDirection == Direction.Forward)
  921. FocusFirst ();
  922. else
  923. FocusLast ();
  924. }
  925. /// <summary>
  926. /// Focuses the first focusable subview if one exists.
  927. /// </summary>
  928. public void FocusFirst ()
  929. {
  930. if (subviews == null) {
  931. SuperView?.SetFocus (this);
  932. return;
  933. }
  934. foreach (var view in subviews) {
  935. if (view.CanFocus) {
  936. SetFocus (view);
  937. return;
  938. }
  939. }
  940. }
  941. /// <summary>
  942. /// Focuses the last focusable subview if one exists.
  943. /// </summary>
  944. public void FocusLast ()
  945. {
  946. if (subviews == null) {
  947. SuperView?.SetFocus (this);
  948. return;
  949. }
  950. for (int i = subviews.Count; i > 0;) {
  951. i--;
  952. View v = subviews [i];
  953. if (v.CanFocus) {
  954. SetFocus (v);
  955. return;
  956. }
  957. }
  958. }
  959. /// <summary>
  960. /// Focuses the previous view.
  961. /// </summary>
  962. /// <returns><c>true</c>, if previous was focused, <c>false</c> otherwise.</returns>
  963. public bool FocusPrev ()
  964. {
  965. FocusDirection = Direction.Backward;
  966. if (subviews == null || subviews.Count == 0)
  967. return false;
  968. if (focused == null) {
  969. FocusLast ();
  970. return focused != null;
  971. }
  972. int focused_idx = -1;
  973. for (int i = subviews.Count; i > 0;) {
  974. i--;
  975. View w = subviews [i];
  976. if (w.HasFocus) {
  977. if (w.FocusPrev ())
  978. return true;
  979. focused_idx = i;
  980. continue;
  981. }
  982. if (w.CanFocus && focused_idx != -1) {
  983. focused.HasFocus = false;
  984. if (w != null && w.CanFocus)
  985. w.FocusLast ();
  986. SetFocus (w);
  987. return true;
  988. }
  989. }
  990. if (focused != null) {
  991. focused.HasFocus = false;
  992. focused = null;
  993. }
  994. return false;
  995. }
  996. /// <summary>
  997. /// Focuses the next view.
  998. /// </summary>
  999. /// <returns><c>true</c>, if next was focused, <c>false</c> otherwise.</returns>
  1000. public bool FocusNext ()
  1001. {
  1002. FocusDirection = Direction.Forward;
  1003. if (subviews == null || subviews.Count == 0)
  1004. return false;
  1005. if (focused == null) {
  1006. FocusFirst ();
  1007. return focused != null;
  1008. }
  1009. int n = subviews.Count;
  1010. int focused_idx = -1;
  1011. for (int i = 0; i < n; i++) {
  1012. View w = subviews [i];
  1013. if (w.HasFocus) {
  1014. if (w.FocusNext ())
  1015. return true;
  1016. focused_idx = i;
  1017. continue;
  1018. }
  1019. if (w.CanFocus && focused_idx != -1) {
  1020. focused.HasFocus = false;
  1021. if (w != null && w.CanFocus)
  1022. w.FocusFirst ();
  1023. SetFocus (w);
  1024. return true;
  1025. }
  1026. }
  1027. if (focused != null) {
  1028. focused.HasFocus = false;
  1029. focused = null;
  1030. }
  1031. return false;
  1032. }
  1033. /// <summary>
  1034. /// Computes the RelativeLayout for the view, given the frame for its container.
  1035. /// </summary>
  1036. /// <param name="hostFrame">The Frame for the host.</param>
  1037. internal void RelativeLayout (Rect hostFrame)
  1038. {
  1039. int w, h, _x, _y;
  1040. if (x is Pos.PosCenter) {
  1041. if (width == null)
  1042. w = hostFrame.Width;
  1043. else
  1044. w = width.Anchor (hostFrame.Width);
  1045. _x = x.Anchor (hostFrame.Width - w);
  1046. } else {
  1047. if (x == null)
  1048. _x = 0;
  1049. else
  1050. _x = x.Anchor (hostFrame.Width);
  1051. if (width == null)
  1052. w = hostFrame.Width;
  1053. else
  1054. w = width.Anchor (hostFrame.Width - _x);
  1055. }
  1056. if (y is Pos.PosCenter) {
  1057. if (height == null)
  1058. h = hostFrame.Height;
  1059. else
  1060. h = height.Anchor (hostFrame.Height);
  1061. _y = y.Anchor (hostFrame.Height - h);
  1062. } else {
  1063. if (y == null)
  1064. _y = 0;
  1065. else
  1066. _y = y.Anchor (hostFrame.Height);
  1067. if (height == null)
  1068. h = hostFrame.Height;
  1069. else
  1070. h = height.Anchor (hostFrame.Height - _y);
  1071. }
  1072. Frame = new Rect (_x, _y, w, h);
  1073. // layoutNeeded = false;
  1074. }
  1075. // https://en.wikipedia.org/wiki/Topological_sorting
  1076. List<View> TopologicalSort (HashSet<View> nodes, HashSet<(View From, View To)> edges)
  1077. {
  1078. var result = new List<View> ();
  1079. // Set of all nodes with no incoming edges
  1080. var S = new HashSet<View> (nodes.Where (n => edges.All (e => e.To.Equals (n) == false)));
  1081. while (S.Any ()) {
  1082. // remove a node n from S
  1083. var n = S.First ();
  1084. S.Remove (n);
  1085. // add n to tail of L
  1086. if (n != this?.SuperView)
  1087. result.Add (n);
  1088. // for each node m with an edge e from n to m do
  1089. foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
  1090. var m = e.To;
  1091. // remove edge e from the graph
  1092. edges.Remove (e);
  1093. // if m has no other incoming edges then
  1094. if (edges.All (me => me.To.Equals (m) == false) && m != this?.SuperView) {
  1095. // insert m into S
  1096. S.Add (m);
  1097. }
  1098. }
  1099. }
  1100. // if graph has edges then
  1101. if (edges.Any ()) {
  1102. // return error (graph has at least one cycle)
  1103. return null;
  1104. } else {
  1105. // return L (a topologically sorted order)
  1106. return result;
  1107. }
  1108. }
  1109. /// <summary>
  1110. /// This virtual method is invoked when a view starts executing or
  1111. /// when the dimensions of the view have changed, for example in
  1112. /// response to the container view or terminal resizing.
  1113. /// </summary>
  1114. public virtual void LayoutSubviews ()
  1115. {
  1116. if (!layoutNeeded)
  1117. return;
  1118. // Sort out the dependencies of the X, Y, Width, Height properties
  1119. var nodes = new HashSet<View> ();
  1120. var edges = new HashSet<(View, View)> ();
  1121. foreach (var v in InternalSubviews) {
  1122. nodes.Add (v);
  1123. if (v.LayoutStyle == LayoutStyle.Computed) {
  1124. if (v.X is Pos.PosView vX)
  1125. edges.Add ((vX.Target, v));
  1126. if (v.Y is Pos.PosView vY)
  1127. edges.Add ((vY.Target, v));
  1128. if (v.Width is Dim.DimView vWidth)
  1129. edges.Add ((vWidth.Target, v));
  1130. if (v.Height is Dim.DimView vHeight)
  1131. edges.Add ((vHeight.Target, v));
  1132. }
  1133. }
  1134. var ordered = TopologicalSort (nodes, edges);
  1135. if (ordered == null)
  1136. throw new Exception ("There is a recursive cycle in the relative Pos/Dim in the views of " + this);
  1137. foreach (var v in ordered) {
  1138. if (v.LayoutStyle == LayoutStyle.Computed)
  1139. v.RelativeLayout (Frame);
  1140. v.LayoutSubviews ();
  1141. v.layoutNeeded = false;
  1142. }
  1143. if (SuperView == Application.Top && layoutNeeded && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
  1144. RelativeLayout (Frame);
  1145. }
  1146. layoutNeeded = false;
  1147. }
  1148. /// <inheritdoc cref="ToString"/>
  1149. public override string ToString ()
  1150. {
  1151. return $"{GetType ().Name}({Id})({Frame})";
  1152. }
  1153. /// <inheritdoc cref="OnMouseEnter(Gui.MouseEvent)"/>
  1154. public override bool OnMouseEnter (MouseEvent mouseEvent)
  1155. {
  1156. if (!base.OnMouseEnter (mouseEvent)) {
  1157. MouseEnter?.Invoke (this, mouseEvent);
  1158. return false;
  1159. }
  1160. return true;
  1161. }
  1162. /// <inheritdoc cref="OnMouseLeave(Gui.MouseEvent)"/>
  1163. public override bool OnMouseLeave (MouseEvent mouseEvent)
  1164. {
  1165. if (!base.OnMouseLeave (mouseEvent)) {
  1166. MouseLeave?.Invoke (this, mouseEvent);
  1167. return false;
  1168. }
  1169. return true;
  1170. }
  1171. }
  1172. }