View.Drawing.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. #nullable enable
  2. using System.Diagnostics;
  3. namespace Terminal.Gui;
  4. public partial class View // Drawing APIs
  5. {
  6. private ColorScheme? _colorScheme;
  7. /// <summary>The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s color scheme.</summary>
  8. public virtual ColorScheme? ColorScheme
  9. {
  10. get
  11. {
  12. if (_colorScheme is null)
  13. {
  14. return SuperView?.ColorScheme;
  15. }
  16. return _colorScheme;
  17. }
  18. set
  19. {
  20. if (_colorScheme != value)
  21. {
  22. _colorScheme = value;
  23. if (Border is { } && Border.LineStyle != LineStyle.None && Border.ColorScheme is { })
  24. {
  25. Border.ColorScheme = _colorScheme;
  26. }
  27. SetNeedsDisplay ();
  28. }
  29. }
  30. }
  31. /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
  32. /// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
  33. public LineCanvas LineCanvas { get; } = new ();
  34. /// <summary>
  35. /// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for rendering any
  36. /// lines. If <see langword="true"/> the rendering of any borders drawn by this Frame will be done by its parent's
  37. /// SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawAdornments"/> method will be
  38. /// called to render the borders.
  39. /// </summary>
  40. public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
  41. /// <summary>Draws the specified character in the specified viewport-relative column and row of the View.</summary>
  42. /// <para>
  43. /// If the provided coordinates are outside the visible content area, this method does nothing.
  44. /// </para>
  45. /// <remarks>
  46. /// The top-left corner of the visible content area is <c>ViewPort.Location</c>.
  47. /// </remarks>
  48. /// <param name="col">Column (viewport-relative).</param>
  49. /// <param name="row">Row (viewport-relative).</param>
  50. /// <param name="rune">The Rune.</param>
  51. public void AddRune (int col, int row, Rune rune)
  52. {
  53. if (Move (col, row))
  54. {
  55. Driver?.AddRune (rune);
  56. }
  57. }
  58. /// <summary>Clears <see cref="Viewport"/> with the normal background.</summary>
  59. /// <remarks>
  60. /// <para>
  61. /// If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClearContentOnly"/> only
  62. /// the portion of the content
  63. /// area that is visible within the <see cref="View.Viewport"/> will be cleared. This is useful for views that have
  64. /// a
  65. /// content area larger than the Viewport (e.g. when <see cref="ViewportSettings.AllowNegativeLocation"/> is
  66. /// enabled) and want
  67. /// the area outside the content to be visually distinct.
  68. /// </para>
  69. /// </remarks>
  70. public void Clear ()
  71. {
  72. if (Driver is null)
  73. {
  74. return;
  75. }
  76. // Get screen-relative coords
  77. Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) });
  78. Rectangle prevClip = Driver.Clip;
  79. if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly))
  80. {
  81. Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
  82. toClear = Rectangle.Intersect (toClear, visibleContent);
  83. }
  84. Attribute prev = Driver.SetAttribute (GetNormalColor ());
  85. Driver.FillRect (toClear);
  86. Driver.SetAttribute (prev);
  87. Driver.Clip = prevClip;
  88. }
  89. /// <summary>Fills the specified <see cref="Viewport"/>-relative rectangle with the specified color.</summary>
  90. /// <param name="rect">The Viewport-relative rectangle to clear.</param>
  91. /// <param name="color">The color to use to fill the rectangle. If not provided, the Normal background color will be used.</param>
  92. public void FillRect (Rectangle rect, Color? color = null)
  93. {
  94. if (Driver is null)
  95. {
  96. return;
  97. }
  98. // Get screen-relative coords
  99. Rectangle toClear = ViewportToScreen (rect);
  100. Rectangle prevClip = Driver.Clip;
  101. Driver.Clip = Rectangle.Intersect (prevClip, ViewportToScreen (Viewport with { Location = new (0, 0) }));
  102. Attribute prev = Driver.SetAttribute (new (color ?? GetNormalColor ().Background));
  103. Driver.FillRect (toClear);
  104. Driver.SetAttribute (prev);
  105. Driver.Clip = prevClip;
  106. }
  107. /// <summary>Sets the <see cref="ConsoleDriver"/>'s clip region to <see cref="Viewport"/>.</summary>
  108. /// <remarks>
  109. /// <para>
  110. /// By default, the clip rectangle is set to the intersection of the current clip region and the
  111. /// <see cref="Viewport"/>. This ensures that drawing is constrained to the viewport, but allows
  112. /// content to be drawn beyond the viewport.
  113. /// </para>
  114. /// <para>
  115. /// If <see cref="ViewportSettings"/> has <see cref="Gui.ViewportSettings.ClipContentOnly"/> set, clipping will be
  116. /// applied to just the visible content area.
  117. /// </para>
  118. /// </remarks>
  119. /// <returns>
  120. /// The current screen-relative clip region, which can be then re-applied by setting
  121. /// <see cref="ConsoleDriver.Clip"/>.
  122. /// </returns>
  123. public Rectangle SetClip ()
  124. {
  125. if (Driver is null)
  126. {
  127. return Rectangle.Empty;
  128. }
  129. Rectangle previous = Driver.Clip;
  130. // Clamp the Clip to the entire visible area
  131. Rectangle clip = Rectangle.Intersect (ViewportToScreen (Viewport with { Location = Point.Empty }), previous);
  132. if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly))
  133. {
  134. // Clamp the Clip to the just content area that is within the viewport
  135. Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
  136. clip = Rectangle.Intersect (clip, visibleContent);
  137. }
  138. Driver.Clip = clip;
  139. return previous;
  140. }
  141. /// <summary>
  142. /// Draws the view if it needs to be drawn. Causes the following virtual methods to be called (along with their related events):
  143. /// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
  144. /// </summary>
  145. /// <remarks>
  146. /// <para>
  147. /// The view will only be drawn if it is visible, and has any of <see cref="NeedsDisplay"/>, <see cref="SubViewNeedsDisplay"/>,
  148. /// or <see cref="IsLayoutNeeded"/> set.
  149. /// </para>
  150. /// <para>
  151. /// Always use <see cref="Viewport"/> (view-relative) when calling <see cref="OnDrawContent(Rectangle)"/>, NOT
  152. /// <see cref="Frame"/> (superview-relative).
  153. /// </para>
  154. /// <para>
  155. /// Views should set the color that they want to use on entry, as otherwise this will inherit the last color that
  156. /// was set globally on the driver.
  157. /// </para>
  158. /// <para>
  159. /// Overrides of <see cref="OnDrawContent(Rectangle)"/> must ensure they do not set <c>Driver.Clip</c> to a clip
  160. /// region larger than the <ref name="Viewport"/> property, as this will cause the driver to clip the entire
  161. /// region.
  162. /// </para>
  163. /// </remarks>
  164. public void Draw ()
  165. {
  166. if (!CanBeVisible (this))
  167. {
  168. return;
  169. }
  170. if (IsLayoutNeeded ())
  171. {
  172. //Debug.WriteLine ($"Layout should be de-coupled from drawing: {this}");
  173. }
  174. //// TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
  175. //// TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
  176. //if ((this != Application.Top || this is Toplevel { Modal: true }) && Arrangement.HasFlag (ViewArrangement.Overlapped))
  177. //{
  178. // SetNeedsDisplay ();
  179. //}
  180. if (!NeedsDisplay && !SubViewNeedsDisplay)
  181. {
  182. return;
  183. }
  184. OnDrawAdornments ();
  185. if (ColorScheme is { })
  186. {
  187. //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
  188. Driver?.SetAttribute (GetNormalColor ());
  189. }
  190. // By default, we clip to the viewport preventing drawing outside the viewport
  191. // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
  192. // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
  193. Rectangle prevClip = SetClip ();
  194. // Invoke DrawContentEvent
  195. var dev = new DrawEventArgs (Viewport, Rectangle.Empty);
  196. DrawContent?.Invoke (this, dev);
  197. if (!dev.Cancel)
  198. {
  199. OnDrawContent (Viewport);
  200. }
  201. if (Driver is { })
  202. {
  203. Driver.Clip = prevClip;
  204. }
  205. OnRenderLineCanvas ();
  206. // TODO: This is a hack to force the border subviews to draw.
  207. if (Border?.Subviews is { })
  208. {
  209. foreach (View view in Border.Subviews)
  210. {
  211. view.SetNeedsDisplay ();
  212. view.Draw ();
  213. }
  214. }
  215. // Invoke DrawContentCompleteEvent
  216. OnDrawContentComplete (Viewport);
  217. ClearNeedsDisplay ();
  218. }
  219. /// <summary>Event invoked when the content area of the View is to be drawn.</summary>
  220. /// <remarks>
  221. /// <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
  222. /// <para>
  223. /// Rect provides the view-relative rectangle describing the currently visible viewport into the
  224. /// <see cref="View"/> .
  225. /// </para>
  226. /// </remarks>
  227. public event EventHandler<DrawEventArgs>? DrawContent;
  228. /// <summary>Event invoked when the content area of the View is completed drawing.</summary>
  229. /// <remarks>
  230. /// <para>Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.</para>
  231. /// <para>
  232. /// Rect provides the view-relative rectangle describing the currently visible viewport into the
  233. /// <see cref="View"/> .
  234. /// </para>
  235. /// </remarks>
  236. public event EventHandler<DrawEventArgs>? DrawContentComplete;
  237. /// <summary>Utility function to draw strings that contain a hotkey.</summary>
  238. /// <param name="text">String to display, the hotkey specifier before a letter flags the next letter as the hotkey.</param>
  239. /// <param name="hotColor">Hot color.</param>
  240. /// <param name="normalColor">Normal color.</param>
  241. /// <remarks>
  242. /// <para>
  243. /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
  244. /// default.
  245. /// </para>
  246. /// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
  247. /// </remarks>
  248. public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
  249. {
  250. Rune hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
  251. Application.Driver?.SetAttribute (normalColor);
  252. foreach (Rune rune in text.EnumerateRunes ())
  253. {
  254. if (rune == new Rune (hotkeySpec.Value))
  255. {
  256. Application.Driver?.SetAttribute (hotColor);
  257. continue;
  258. }
  259. Application.Driver?.AddRune (rune);
  260. Application.Driver?.SetAttribute (normalColor);
  261. }
  262. }
  263. /// <summary>
  264. /// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused"
  265. /// state.
  266. /// </summary>
  267. /// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
  268. /// <param name="focused">
  269. /// If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
  270. /// the regular ones.
  271. /// </param>
  272. public void DrawHotString (string text, bool focused)
  273. {
  274. if (focused)
  275. {
  276. DrawHotString (text, GetHotFocusColor (), GetFocusColor ());
  277. }
  278. else
  279. {
  280. DrawHotString (
  281. text,
  282. Enabled ? GetHotNormalColor () : ColorScheme!.Disabled,
  283. Enabled ? GetNormalColor () : ColorScheme!.Disabled
  284. );
  285. }
  286. }
  287. /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
  288. /// <returns>
  289. /// <see cref="ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/> or
  290. /// <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
  291. /// overridden can return other values.
  292. /// </returns>
  293. public virtual Attribute GetFocusColor ()
  294. {
  295. ColorScheme? cs = ColorScheme;
  296. if (cs is null)
  297. {
  298. cs = new ();
  299. }
  300. return Enabled ? GetColor (cs.Focus) : cs.Disabled;
  301. }
  302. /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
  303. /// <returns>
  304. /// <see cref="ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/> or
  305. /// <see cref="ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
  306. /// overridden can return other values.
  307. /// </returns>
  308. public virtual Attribute GetHotFocusColor ()
  309. {
  310. ColorScheme? cs = ColorScheme ?? new ();
  311. return Enabled ? GetColor (cs.HotFocus) : cs.Disabled;
  312. }
  313. /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
  314. /// <returns>
  315. /// <see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/> or
  316. /// <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
  317. /// overridden can return other values.
  318. /// </returns>
  319. public virtual Attribute GetHotNormalColor ()
  320. {
  321. ColorScheme? cs = ColorScheme;
  322. if (cs is null)
  323. {
  324. cs = new ();
  325. }
  326. return Enabled ? GetColor (cs.HotNormal) : cs.Disabled;
  327. }
  328. /// <summary>Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.</summary>
  329. /// <returns>
  330. /// <see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/> or
  331. /// <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>. If it's
  332. /// overridden can return other values.
  333. /// </returns>
  334. public virtual Attribute GetNormalColor ()
  335. {
  336. ColorScheme? cs = ColorScheme;
  337. if (cs is null)
  338. {
  339. cs = new ();
  340. }
  341. Attribute disabled = new (cs.Disabled.Foreground, cs.Disabled.Background);
  342. if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
  343. {
  344. disabled = new (disabled.Foreground.GetDarkerColor (), disabled.Background.GetDarkerColor ());
  345. }
  346. return Enabled ? GetColor (cs.Normal) : disabled;
  347. }
  348. private Attribute GetColor (Attribute inputAttribute)
  349. {
  350. Attribute attr = inputAttribute;
  351. if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
  352. {
  353. attr = new (attr.Foreground.GetDarkerColor (), attr.Background.GetDarkerColor ());
  354. }
  355. return attr;
  356. }
  357. /// <summary>Moves the drawing cursor to the specified <see cref="Viewport"/>-relative location in the view.</summary>
  358. /// <remarks>
  359. /// <para>
  360. /// If the provided coordinates are outside the visible content area, this method does nothing.
  361. /// </para>
  362. /// <para>
  363. /// The top-left corner of the visible content area is <c>ViewPort.Location</c>.
  364. /// </para>
  365. /// </remarks>
  366. /// <param name="col">Column (viewport-relative).</param>
  367. /// <param name="row">Row (viewport-relative).</param>
  368. public bool Move (int col, int row)
  369. {
  370. if (Driver is null || Driver?.Rows == 0)
  371. {
  372. return false;
  373. }
  374. if (col < 0 || row < 0 || col >= Viewport.Width || row >= Viewport.Height)
  375. {
  376. return false;
  377. }
  378. Point screen = ViewportToScreen (new Point (col, row));
  379. Driver?.Move (screen.X, screen.Y);
  380. return true;
  381. }
  382. // TODO: Make this cancelable
  383. /// <summary>
  384. /// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
  385. /// <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
  386. /// false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
  387. /// </summary>
  388. /// <returns></returns>
  389. public virtual bool OnDrawAdornments ()
  390. {
  391. // Each of these renders lines to either this View's LineCanvas
  392. // Those lines will be finally rendered in OnRenderLineCanvas
  393. // QUESTION: Why are we not calling Draw here?
  394. Margin?.OnDrawContent (Margin.Viewport);
  395. Border?.OnDrawContent (Border.Viewport);
  396. Padding?.OnDrawContent (Padding.Viewport);
  397. return true;
  398. }
  399. /// <summary>
  400. /// Draws the view's content, including Subviews.
  401. /// </summary>
  402. /// <remarks>
  403. /// <para>
  404. /// The <paramref name="viewport"/> parameter is provided as a convenience; it has the same values as the
  405. /// <see cref="Viewport"/> property.
  406. /// </para>
  407. /// <para>
  408. /// The <see cref="Viewport"/> Location and Size indicate what part of the View's content, defined
  409. /// by <see cref="GetContentSize ()"/>, is visible and should be drawn. The coordinates taken by <see cref="Move"/>
  410. /// and
  411. /// <see cref="AddRune"/> are relative to <see cref="Viewport"/>, thus if <c>ViewPort.Location.Y</c> is <c>5</c>
  412. /// the 6th row of the content should be drawn using <c>MoveTo (x, 5)</c>.
  413. /// </para>
  414. /// <para>
  415. /// If <see cref="GetContentSize ()"/> is larger than <c>ViewPort.Size</c> drawing code should use
  416. /// <see cref="Viewport"/>
  417. /// to constrain drawing for better performance.
  418. /// </para>
  419. /// <para>
  420. /// The <see cref="ConsoleDriver.Clip"/> may define smaller area than <see cref="Viewport"/>; complex drawing code
  421. /// can be more
  422. /// efficient by using <see cref="ConsoleDriver.Clip"/> to constrain drawing for better performance.
  423. /// </para>
  424. /// <para>
  425. /// Overrides should loop through the subviews and call <see cref="Draw"/>.
  426. /// </para>
  427. /// </remarks>
  428. /// <param name="viewport">
  429. /// The rectangle describing the currently visible viewport into the <see cref="View"/>; has the same value as
  430. /// <see cref="Viewport"/>.
  431. /// </param>
  432. public virtual void OnDrawContent (Rectangle viewport)
  433. {
  434. if (!CanBeVisible (this))
  435. {
  436. return;
  437. }
  438. // BUGBUG: this clears way too frequently. Need to optimize this.
  439. if (NeedsDisplay/* || Arrangement.HasFlag (ViewArrangement.Overlapped)*/)
  440. {
  441. Clear ();
  442. }
  443. if (!string.IsNullOrEmpty (TextFormatter.Text))
  444. {
  445. TextFormatter.NeedsFormat = true;
  446. }
  447. // This should NOT clear
  448. // TODO: If the output is not in the Viewport, do nothing
  449. var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
  450. TextFormatter?.Draw (
  451. drawRect,
  452. HasFocus ? GetFocusColor () : GetNormalColor (),
  453. HasFocus ? GetHotFocusColor () : GetHotNormalColor (),
  454. Rectangle.Empty
  455. );
  456. SetSubViewNeedsDisplay ();
  457. // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method
  458. // Draw subviews
  459. // TODO: Implement OnDrawSubviews (cancelable);
  460. if (_subviews is { } && SubViewNeedsDisplay)
  461. {
  462. IEnumerable<View> subviewsNeedingDraw = _subviews.Where (
  463. view => view.Visible
  464. && (view.NeedsDisplay
  465. || view.SubViewNeedsDisplay
  466. // || view.Arrangement.HasFlag (ViewArrangement.Overlapped)
  467. ));
  468. foreach (View view in subviewsNeedingDraw)
  469. {
  470. if (view.IsLayoutNeeded ())
  471. {
  472. //Debug.WriteLine ($"Layout should be de-coupled from drawing: {view}");
  473. //view.LayoutSubviews ();
  474. }
  475. // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
  476. // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
  477. if (view.Arrangement.HasFlag (ViewArrangement.Overlapped))
  478. {
  479. // view.SetNeedsDisplay ();
  480. }
  481. view.Draw ();
  482. }
  483. }
  484. }
  485. /// <summary>
  486. /// Called after <see cref="OnDrawContent"/> to enable overrides.
  487. /// </summary>
  488. /// <param name="viewport">
  489. /// The viewport-relative rectangle describing the currently visible viewport into the
  490. /// <see cref="View"/>
  491. /// </param>
  492. public virtual void OnDrawContentComplete (Rectangle viewport) { DrawContentComplete?.Invoke (this, new (viewport, Rectangle.Empty)); }
  493. // TODO: Make this cancelable
  494. /// <summary>
  495. /// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
  496. /// <see cref="LineCanvas"/> of this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
  497. /// false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
  498. /// </summary>
  499. /// <returns></returns>
  500. public virtual bool OnRenderLineCanvas ()
  501. {
  502. if (Driver is null)
  503. {
  504. return false;
  505. }
  506. // If we have a SuperView, it'll render our frames.
  507. if (!SuperViewRendersLineCanvas && LineCanvas.Viewport != Rectangle.Empty)
  508. {
  509. foreach (KeyValuePair<Point, Cell?> p in LineCanvas.GetCellMap ())
  510. {
  511. // Get the entire map
  512. if (p.Value is { })
  513. {
  514. Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
  515. Driver.Move (p.Key.X, p.Key.Y);
  516. // TODO: #2616 - Support combining sequences that don't normalize
  517. Driver.AddRune (p.Value.Value.Rune);
  518. }
  519. }
  520. LineCanvas.Clear ();
  521. }
  522. if (Subviews.Any (s => s.SuperViewRendersLineCanvas))
  523. {
  524. foreach (View subview in Subviews.Where (s => s.SuperViewRendersLineCanvas))
  525. {
  526. // Combine the LineCanvas'
  527. LineCanvas.Merge (subview.LineCanvas);
  528. subview.LineCanvas.Clear ();
  529. }
  530. foreach (KeyValuePair<Point, Cell?> p in LineCanvas.GetCellMap ())
  531. {
  532. // Get the entire map
  533. if (p.Value is { })
  534. {
  535. Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
  536. Driver.Move (p.Key.X, p.Key.Y);
  537. // TODO: #2616 - Support combining sequences that don't normalize
  538. Driver.AddRune (p.Value.Value.Rune);
  539. }
  540. }
  541. LineCanvas.Clear ();
  542. }
  543. return true;
  544. }
  545. #region NeedsDisplay
  546. // The viewport-relative region that needs to be redrawn. Marked internal for unit tests.
  547. internal Rectangle _needsDisplayRect = Rectangle.Empty;
  548. /// <summary>Gets or sets whether the view needs to be redrawn.</summary>
  549. public bool NeedsDisplay
  550. {
  551. get => _needsDisplayRect != Rectangle.Empty || IsLayoutNeeded ();
  552. set
  553. {
  554. if (value)
  555. {
  556. SetNeedsDisplay ();
  557. }
  558. else
  559. {
  560. ClearNeedsDisplay ();
  561. }
  562. }
  563. }
  564. /// <summary>Gets whether any Subviews need to be redrawn.</summary>
  565. public bool SubViewNeedsDisplay { get; private set; }
  566. /// <summary>Sets that the <see cref="Viewport"/> of this View needs to be redrawn.</summary>
  567. /// <remarks>
  568. /// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), this method
  569. /// does nothing.
  570. /// </remarks>
  571. public void SetNeedsDisplay ()
  572. {
  573. Rectangle viewport = Viewport;
  574. if (_needsDisplayRect != Rectangle.Empty && viewport.IsEmpty)
  575. {
  576. // This handles the case where the view has not been initialized yet
  577. return;
  578. }
  579. SetNeedsDisplay (viewport);
  580. }
  581. /// <summary>Expands the area of this view needing to be redrawn to include <paramref name="viewPortRelativeRegion"/>.</summary>
  582. /// <remarks>
  583. /// <para>
  584. /// The location of <paramref name="viewPortRelativeRegion"/> is relative to the View's <see cref="Viewport"/>.
  585. /// </para>
  586. /// <para>
  587. /// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>), the area to be
  588. /// redrawn will be the <paramref name="viewPortRelativeRegion"/>.
  589. /// </para>
  590. /// </remarks>
  591. /// <param name="viewPortRelativeRegion">The <see cref="Viewport"/>relative region that needs to be redrawn.</param>
  592. public void SetNeedsDisplay (Rectangle viewPortRelativeRegion)
  593. {
  594. if (_needsDisplayRect.IsEmpty)
  595. {
  596. _needsDisplayRect = viewPortRelativeRegion;
  597. }
  598. else
  599. {
  600. int x = Math.Min (Viewport.X, viewPortRelativeRegion.X);
  601. int y = Math.Min (Viewport.Y, viewPortRelativeRegion.Y);
  602. int w = Math.Max (Viewport.Width, viewPortRelativeRegion.Width);
  603. int h = Math.Max (Viewport.Height, viewPortRelativeRegion.Height);
  604. _needsDisplayRect = new (x, y, w, h);
  605. }
  606. Margin?.SetNeedsDisplay ();
  607. Border?.SetNeedsDisplay ();
  608. Padding?.SetNeedsDisplay ();
  609. SuperView?.SetSubViewNeedsDisplay ();
  610. if (this is Adornment adornment)
  611. {
  612. adornment.Parent?.SetSubViewNeedsDisplay ();
  613. }
  614. foreach (View subview in Subviews)
  615. {
  616. if (subview.Frame.IntersectsWith (viewPortRelativeRegion))
  617. {
  618. Rectangle subviewRegion = Rectangle.Intersect (subview.Frame, viewPortRelativeRegion);
  619. subviewRegion.X -= subview.Frame.X;
  620. subviewRegion.Y -= subview.Frame.Y;
  621. subview.SetNeedsDisplay (subviewRegion);
  622. }
  623. }
  624. }
  625. /// <summary>Sets <see cref="SubViewNeedsDisplay"/> to <see langword="true"/> for this View and all Superviews.</summary>
  626. public void SetSubViewNeedsDisplay ()
  627. {
  628. SubViewNeedsDisplay = true;
  629. if (this is Adornment adornment)
  630. {
  631. adornment.Parent?.SetSubViewNeedsDisplay ();
  632. }
  633. if (SuperView is { SubViewNeedsDisplay: false })
  634. {
  635. SuperView.SetSubViewNeedsDisplay ();
  636. }
  637. }
  638. /// <summary>Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.</summary>
  639. protected void ClearNeedsDisplay ()
  640. {
  641. _needsDisplayRect = Rectangle.Empty;
  642. SubViewNeedsDisplay = false;
  643. Margin?.ClearNeedsDisplay ();
  644. Border?.ClearNeedsDisplay ();
  645. Padding?.ClearNeedsDisplay ();
  646. foreach (View subview in Subviews)
  647. {
  648. subview.ClearNeedsDisplay ();
  649. }
  650. }
  651. #endregion NeedsDisplay
  652. }