View.Drawing.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942
  1. using System.ComponentModel;
  2. namespace Terminal.Gui.ViewBase;
  3. public partial class View // Drawing APIs
  4. {
  5. /// <summary>
  6. /// Draws a set of peer views (views that share the same SuperView).
  7. /// </summary>
  8. /// <param name="views">The peer views to draw.</param>
  9. /// <param name="force">
  10. /// If <see langword="true"/>, <see cref="View.SetNeedsDraw()"/> will be called on each view to force
  11. /// it to be drawn.
  12. /// </param>
  13. internal static void Draw (IEnumerable<View> views, bool force)
  14. {
  15. // **Snapshot once** — every recursion level gets its own frozen array
  16. View [] viewsArray = views.Snapshot ();
  17. // The draw context is used to track the region drawn by each view.
  18. var context = new DrawContext ();
  19. foreach (View view in viewsArray)
  20. {
  21. if (force)
  22. {
  23. view.SetNeedsDraw ();
  24. }
  25. view.Draw (context);
  26. }
  27. // Draw Transparent margins last to ensure they are drawn on top of the content.
  28. Margin.DrawMargins (viewsArray);
  29. // DrawMargins may have caused some views have NeedsDraw/NeedsSubViewDraw set; clear them all.
  30. foreach (View view in viewsArray)
  31. {
  32. view.ClearNeedsDraw ();
  33. }
  34. // After all peer views have been drawn and cleared, we can now clear the SuperView's SubViewNeedsDraw flag.
  35. // ClearNeedsDraw() does not clear SuperView.SubViewNeedsDraw (by design, to avoid premature clearing
  36. // when peer subviews still need drawing), so we must do it here after ALL peers are processed.
  37. // We only clear the flag if ALL the SuperView's SubViews no longer need drawing.
  38. View? lastSuperView = null;
  39. foreach (View view in viewsArray)
  40. {
  41. if (view is not Adornment && view.SuperView is { } && view.SuperView != lastSuperView)
  42. {
  43. // Check if ANY subview of this SuperView still needs drawing
  44. bool anySubViewNeedsDrawing = view.SuperView.InternalSubViews.Any (v => v.NeedsDraw || v.SubViewNeedsDraw);
  45. if (!anySubViewNeedsDrawing)
  46. {
  47. view.SuperView.SubViewNeedsDraw = false;
  48. }
  49. lastSuperView = view.SuperView;
  50. }
  51. }
  52. }
  53. /// <summary>
  54. /// Draws the view if it needs to be drawn.
  55. /// </summary>
  56. /// <remarks>
  57. /// <para>
  58. /// The view will only be drawn if it is visible, and has any of <see cref="NeedsDraw"/>,
  59. /// <see cref="SubViewNeedsDraw"/>,
  60. /// or <see cref="NeedsLayout"/> set.
  61. /// </para>
  62. /// <para>
  63. /// See the View Drawing Deep Dive for more information:
  64. /// <see href="https://gui-cs.github.io/Terminal.Gui/docs/drawing.html"/>.
  65. /// </para>
  66. /// </remarks>
  67. public void Draw (DrawContext? context = null)
  68. {
  69. if (!CanBeVisible (this))
  70. {
  71. return;
  72. }
  73. Region? originalClip = GetClip ();
  74. // TODO: This can be further optimized by checking NeedsDraw below and only
  75. // TODO: clearing, drawing text, drawing content, etc. if it is true.
  76. if (NeedsDraw || SubViewNeedsDraw)
  77. {
  78. // ------------------------------------
  79. // Draw the Border and Padding Adornments.
  80. // Note: Margin with a Shadow is special-cased and drawn in a separate pass to support
  81. // transparent shadows.
  82. DoDrawAdornments (originalClip);
  83. SetClip (originalClip);
  84. // ------------------------------------
  85. // Clear the Viewport
  86. // By default, we clip to the viewport preventing drawing outside the viewport
  87. // We also clip to the content, but if a developer wants to draw outside the viewport, they can do
  88. // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag.
  89. // Get our Viewport in screen coordinates
  90. originalClip = AddViewportToClip ();
  91. // If no context ...
  92. context ??= new ();
  93. SetAttributeForRole (Enabled ? VisualRole.Normal : VisualRole.Disabled);
  94. DoClearViewport (context);
  95. // ------------------------------------
  96. // Draw the SubViews first (order matters: SubViews, Text, Content)
  97. if (SubViewNeedsDraw)
  98. {
  99. DoDrawSubViews (context);
  100. }
  101. // ------------------------------------
  102. // Draw the text
  103. SetAttributeForRole (Enabled ? VisualRole.Normal : VisualRole.Disabled);
  104. DoDrawText (context);
  105. // ------------------------------------
  106. // Draw the content
  107. DoDrawContent (context);
  108. // ------------------------------------
  109. // Draw the line canvas
  110. // Restore the clip before rendering the line canvas and adornment subviews
  111. // because they may draw outside the viewport.
  112. SetClip (originalClip);
  113. originalClip = AddFrameToClip ();
  114. DoRenderLineCanvas (context);
  115. // ------------------------------------
  116. // Re-draw the Border and Padding Adornment SubViews
  117. // HACK: This is a hack to ensure that the Border and Padding Adornment SubViews are drawn after the line canvas.
  118. DoDrawAdornmentsSubViews ();
  119. // ------------------------------------
  120. // Advance the diagnostics draw indicator
  121. Border?.AdvanceDrawIndicator ();
  122. ClearNeedsDraw ();
  123. //if (this is not Adornment && SuperView is not Adornment)
  124. //{
  125. // // Parent
  126. // Debug.Assert (Margin!.Parent == this);
  127. // Debug.Assert (Border!.Parent == this);
  128. // Debug.Assert (Padding!.Parent == this);
  129. // // SubViewNeedsDraw is set to false by ClearNeedsDraw.
  130. // Debug.Assert (SubViewNeedsDraw == false);
  131. // Debug.Assert (Margin!.SubViewNeedsDraw == false);
  132. // Debug.Assert (Border!.SubViewNeedsDraw == false);
  133. // Debug.Assert (Padding!.SubViewNeedsDraw == false);
  134. // // NeedsDraw is set to false by ClearNeedsDraw.
  135. // Debug.Assert (NeedsDraw == false);
  136. // Debug.Assert (Margin!.NeedsDraw == false);
  137. // Debug.Assert (Border!.NeedsDraw == false);
  138. // Debug.Assert (Padding!.NeedsDraw == false);
  139. //}
  140. }
  141. // ------------------------------------
  142. // This causes the Margin to be drawn in a second pass if it has a ShadowStyle
  143. Margin?.CacheClip ();
  144. // ------------------------------------
  145. // Reset the clip to what it was when we started
  146. SetClip (originalClip);
  147. // ------------------------------------
  148. // We're done drawing - The Clip is reset to what it was before we started
  149. // But the context contains the region that was drawn by this view
  150. DoDrawComplete (context);
  151. // When DoDrawComplete returns, Driver.Clip has been updated to exclude this view's area.
  152. // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
  153. // a clip with "holes" where this view (and any SubViews drawn before it) are located.
  154. }
  155. #region DrawAdornments
  156. private void DoDrawAdornmentsSubViews ()
  157. {
  158. // Only SetNeedsDraw on Margin here if it is not Transparent. Transparent Margins are drawn in a separate pass in the static View.Draw
  159. // via Margin.DrawTransparentMargins.
  160. if (Margin is { NeedsDraw: true } && !Margin.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) && Margin.Thickness != Thickness.Empty)
  161. {
  162. foreach (View subview in Margin.SubViews)
  163. {
  164. subview.SetNeedsDraw ();
  165. }
  166. // NOTE: We do not support arbitrary SubViews of Margin (only ShadowView)
  167. // NOTE: so we do not call DoDrawSubViews on Margin.
  168. }
  169. if (Border?.SubViews is { } && Border.Thickness != Thickness.Empty && Border.NeedsDraw)
  170. {
  171. // PERFORMANCE: Get the check for DrawIndicator out of this somehow.
  172. foreach (View subview in Border.SubViews.Where (v => v.Visible || v.Id == "DrawIndicator"))
  173. {
  174. if (subview.Id != "DrawIndicator")
  175. {
  176. subview.SetNeedsDraw ();
  177. }
  178. LineCanvas.Exclude (new (subview.FrameToScreen ()));
  179. }
  180. Region? saved = Border?.AddFrameToClip ();
  181. Border?.DoDrawSubViews ();
  182. SetClip (saved);
  183. }
  184. if (Padding?.SubViews is { } && Padding.Thickness != Thickness.Empty && Padding.NeedsDraw)
  185. {
  186. foreach (View subview in Padding.SubViews)
  187. {
  188. subview.SetNeedsDraw ();
  189. }
  190. Region? saved = Padding?.AddFrameToClip ();
  191. Padding?.DoDrawSubViews ();
  192. SetClip (saved);
  193. }
  194. }
  195. internal void DoDrawAdornments (Region? originalClip)
  196. {
  197. if (this is Adornment)
  198. {
  199. AddFrameToClip ();
  200. }
  201. else
  202. {
  203. // Set the clip to be just the thicknesses of the adornments
  204. // TODO: Put this union logic in a method on View?
  205. Region clipAdornments = Margin!.Thickness.AsRegion (Margin!.FrameToScreen ());
  206. clipAdornments.Combine (Border!.Thickness.AsRegion (Border!.FrameToScreen ()), RegionOp.Union);
  207. clipAdornments.Combine (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ()), RegionOp.Union);
  208. clipAdornments.Combine (originalClip, RegionOp.Intersect);
  209. SetClip (clipAdornments);
  210. }
  211. if (Margin?.NeedsLayout == true)
  212. {
  213. Margin.NeedsLayout = false;
  214. Margin?.Thickness.Draw (Driver, FrameToScreen ());
  215. Margin?.Parent?.SetSubViewNeedsDrawDownHierarchy ();
  216. }
  217. if (SubViewNeedsDraw)
  218. {
  219. // A SubView may add to the LineCanvas. This ensures any Adornment LineCanvas updates happen.
  220. Border?.SetNeedsDraw ();
  221. Padding?.SetNeedsDraw ();
  222. Margin?.SetNeedsDraw ();
  223. }
  224. if (OnDrawingAdornments ())
  225. {
  226. return;
  227. }
  228. // TODO: add event.
  229. DrawAdornments ();
  230. }
  231. /// <summary>
  232. /// Causes <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/> to be drawn.
  233. /// </summary>
  234. /// <remarks>
  235. /// <para>
  236. /// <see cref="Margin"/> is drawn in a separate pass if <see cref="ShadowStyle"/> is set.
  237. /// </para>
  238. /// </remarks>
  239. public void DrawAdornments ()
  240. {
  241. // Only draw Margin here if it is not Transparent. Transparent Margins are drawn in a separate pass in the static View.Draw
  242. // via Margin.DrawTransparentMargins.
  243. if (Margin is { } && !Margin.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) && Margin.Thickness != Thickness.Empty)
  244. {
  245. Margin?.Draw ();
  246. }
  247. // Each of these renders lines to this View's LineCanvas
  248. // Those lines will be finally rendered in OnRenderLineCanvas
  249. if (Border is { } && Border.Thickness != Thickness.Empty)
  250. {
  251. Border?.Draw ();
  252. }
  253. if (Padding is { } && Padding.Thickness != Thickness.Empty)
  254. {
  255. Padding?.Draw ();
  256. }
  257. if (Margin is { } && Margin.Thickness != Thickness.Empty /* && Margin.ShadowStyle == ShadowStyle.None*/)
  258. {
  259. //Margin?.Draw ();
  260. }
  261. }
  262. private void ClearFrame ()
  263. {
  264. if (Driver is null)
  265. {
  266. return;
  267. }
  268. // Get screen-relative coords
  269. Rectangle toClear = FrameToScreen ();
  270. Attribute prev = SetAttribute (GetAttributeForRole (VisualRole.Normal));
  271. Driver.FillRect (toClear);
  272. SetAttribute (prev);
  273. SetNeedsDraw ();
  274. }
  275. /// <summary>
  276. /// Called when the View's Adornments are to be drawn. Prepares <see cref="View.LineCanvas"/>. If
  277. /// <see cref="SuperViewRendersLineCanvas"/> is true, only the
  278. /// <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
  279. /// false (the default), this method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
  280. /// </summary>
  281. /// <returns><see langword="true"/> to stop further drawing of the Adornments.</returns>
  282. protected virtual bool OnDrawingAdornments () => false;
  283. #endregion DrawAdornments
  284. #region ClearViewport
  285. internal void DoClearViewport (DrawContext? context = null)
  286. {
  287. if (!NeedsDraw || ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent) || OnClearingViewport ())
  288. {
  289. return;
  290. }
  291. var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
  292. ClearingViewport?.Invoke (this, dev);
  293. if (dev.Cancel)
  294. {
  295. // BUGBUG: We should add the Viewport to context.DrawRegion here?
  296. SetNeedsDraw ();
  297. return;
  298. }
  299. if (!ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
  300. {
  301. ClearViewport (context);
  302. OnClearedViewport ();
  303. ClearedViewport?.Invoke (this, new (Viewport, Viewport, null));
  304. }
  305. }
  306. /// <summary>
  307. /// Called when the <see cref="Viewport"/> is to be cleared.
  308. /// </summary>
  309. /// <returns><see langword="true"/> to stop further clearing.</returns>
  310. protected virtual bool OnClearingViewport () => false;
  311. /// <summary>Event invoked when the <see cref="Viewport"/> is to be cleared.</summary>
  312. /// <remarks>
  313. /// <para>Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.</para>
  314. /// <para>
  315. /// Rect provides the view-relative rectangle describing the currently visible viewport into the
  316. /// <see cref="View"/> .
  317. /// </para>
  318. /// </remarks>
  319. public event EventHandler<DrawEventArgs>? ClearingViewport;
  320. /// <summary>
  321. /// Called when the <see cref="Viewport"/> has been cleared
  322. /// </summary>
  323. protected virtual void OnClearedViewport () { }
  324. /// <summary>Event invoked when the <see cref="Viewport"/> has been cleared.</summary>
  325. public event EventHandler<DrawEventArgs>? ClearedViewport;
  326. /// <summary>Clears <see cref="Viewport"/> with the normal background.</summary>
  327. /// <remarks>
  328. /// <para>
  329. /// If <see cref="ViewportSettings"/> has <see cref="ViewBase.ViewportSettingsFlags.ClearContentOnly"/> only
  330. /// the portion of the content
  331. /// area that is visible within the <see cref="View.Viewport"/> will be cleared. This is useful for views that have
  332. /// a
  333. /// content area larger than the Viewport (e.g. when <see cref="ViewportSettingsFlags.AllowNegativeLocation"/> is
  334. /// enabled) and want
  335. /// the area outside the content to be visually distinct.
  336. /// </para>
  337. /// </remarks>
  338. public void ClearViewport (DrawContext? context = null)
  339. {
  340. if (Driver is null)
  341. {
  342. return;
  343. }
  344. // Get screen-relative coords
  345. Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) });
  346. if (ViewportSettings.HasFlag (ViewportSettingsFlags.ClearContentOnly))
  347. {
  348. Rectangle visibleContent = ViewportToScreen (new Rectangle (new (-Viewport.X, -Viewport.Y), GetContentSize ()));
  349. toClear = Rectangle.Intersect (toClear, visibleContent);
  350. }
  351. Driver.FillRect (toClear);
  352. // context.AddDrawnRectangle (toClear);
  353. SetNeedsDraw ();
  354. }
  355. #endregion ClearViewport
  356. #region DrawText
  357. private void DoDrawText (DrawContext? context = null)
  358. {
  359. if (!NeedsDraw)
  360. {
  361. return;
  362. }
  363. if (!string.IsNullOrEmpty (TextFormatter.Text))
  364. {
  365. TextFormatter.NeedsFormat = true;
  366. }
  367. if (OnDrawingText (context))
  368. {
  369. return;
  370. }
  371. // TODO: Get rid of this vf in lieu of the one above
  372. if (OnDrawingText ())
  373. {
  374. return;
  375. }
  376. var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
  377. DrawingText?.Invoke (this, dev);
  378. if (dev.Cancel)
  379. {
  380. return;
  381. }
  382. DrawText (context);
  383. OnDrewText ();
  384. DrewText?.Invoke (this, EventArgs.Empty);
  385. }
  386. /// <summary>
  387. /// Called when the <see cref="Text"/> of the View is to be drawn.
  388. /// </summary>
  389. /// <param name="context">The draw context to report drawn areas to.</param>
  390. /// <returns><see langword="true"/> to stop further drawing of <see cref="Text"/>.</returns>
  391. protected virtual bool OnDrawingText (DrawContext? context) => false;
  392. /// <summary>
  393. /// Called when the <see cref="Text"/> of the View is to be drawn.
  394. /// </summary>
  395. /// <returns><see langword="true"/> to stop further drawing of <see cref="Text"/>.</returns>
  396. protected virtual bool OnDrawingText () => false;
  397. /// <summary>Raised when the <see cref="Text"/> of the View is to be drawn.</summary>
  398. /// <returns>
  399. /// Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> to stop further drawing of
  400. /// <see cref="Text"/>.
  401. /// </returns>
  402. public event EventHandler<DrawEventArgs>? DrawingText;
  403. /// <summary>
  404. /// Draws the <see cref="Text"/> of the View using the <see cref="TextFormatter"/>.
  405. /// </summary>
  406. /// <param name="context">The draw context to report drawn areas to.</param>
  407. public void DrawText (DrawContext? context = null)
  408. {
  409. var drawRect = new Rectangle (ContentToScreen (Point.Empty), GetContentSize ());
  410. // Use GetDrawRegion to get precise drawn areas
  411. Region textRegion = TextFormatter.GetDrawRegion (drawRect);
  412. // Report the drawn area to the context
  413. context?.AddDrawnRegion (textRegion);
  414. if (Driver is { })
  415. {
  416. TextFormatter.Draw (
  417. Driver,
  418. drawRect,
  419. HasFocus ? GetAttributeForRole (VisualRole.Focus) : GetAttributeForRole (VisualRole.Normal),
  420. HasFocus ? GetAttributeForRole (VisualRole.HotFocus) : GetAttributeForRole (VisualRole.HotNormal),
  421. Rectangle.Empty);
  422. }
  423. // We assume that the text has been drawn over the entire area; ensure that the SubViews are redrawn.
  424. SetSubViewNeedsDrawDownHierarchy ();
  425. }
  426. /// <summary>
  427. /// Called when the <see cref="Text"/> of the View has been drawn.
  428. /// </summary>
  429. protected virtual void OnDrewText () { }
  430. /// <summary>Raised when the <see cref="Text"/> of the View has been drawn.</summary>
  431. public event EventHandler? DrewText;
  432. #endregion DrawText
  433. #region DrawContent
  434. private void DoDrawContent (DrawContext? context = null)
  435. {
  436. if (!NeedsDraw || OnDrawingContent (context))
  437. {
  438. return;
  439. }
  440. var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
  441. DrawingContent?.Invoke (this, dev);
  442. if (dev.Cancel)
  443. { }
  444. // No default drawing; let event handlers or overrides handle it
  445. }
  446. /// <summary>
  447. /// Called when the View's content is to be drawn. The default implementation does nothing.
  448. /// </summary>
  449. /// <param name="context">The draw context to report drawn areas to.</param>
  450. /// <returns><see langword="true"/> to stop further drawing content.</returns>
  451. /// <remarks>
  452. /// <para>
  453. /// Override this method to draw custom content for your View.
  454. /// </para>
  455. /// <para>
  456. /// <b>Transparency Support:</b> If your View has <see cref="ViewportSettings"/> with
  457. /// <see cref="ViewportSettingsFlags.Transparent"/>
  458. /// set, you should report the exact regions you draw to via the <paramref name="context"/> parameter. This allows
  459. /// the transparency system to exclude only the drawn areas from the clip region, letting views beneath show
  460. /// through
  461. /// in the areas you didn't draw.
  462. /// </para>
  463. /// <para>
  464. /// Use <see cref="DrawContext.AddDrawnRectangle"/> for simple rectangular areas, or
  465. /// <see cref="DrawContext.AddDrawnRegion"/>
  466. /// for complex, non-rectangular shapes. All coordinates passed to these methods must be in
  467. /// <b>screen-relative coordinates</b>.
  468. /// Use <see cref="View.ViewportToScreen(in Rectangle)"/> or <see cref="View.ContentToScreen(in Point)"/> to
  469. /// convert from
  470. /// viewport-relative or content-relative coordinates.
  471. /// </para>
  472. /// <para>
  473. /// Example of drawing custom content with transparency support:
  474. /// </para>
  475. /// <code>
  476. /// protected override bool OnDrawingContent (DrawContext? context)
  477. /// {
  478. /// base.OnDrawingContent (context);
  479. ///
  480. /// // Draw content in viewport-relative coordinates
  481. /// Rectangle rect1 = new Rectangle (5, 5, 10, 3);
  482. /// Rectangle rect2 = new Rectangle (8, 8, 4, 7);
  483. /// FillRect (rect1, Glyphs.BlackCircle);
  484. /// FillRect (rect2, Glyphs.BlackCircle);
  485. ///
  486. /// // Report drawn region in screen-relative coordinates for transparency
  487. /// if (ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
  488. /// {
  489. /// Region drawnRegion = new Region (ViewportToScreen (rect1));
  490. /// drawnRegion.Union (ViewportToScreen (rect2));
  491. /// context?.AddDrawnRegion (drawnRegion);
  492. /// }
  493. ///
  494. /// return true;
  495. /// }
  496. /// </code>
  497. /// </remarks>
  498. protected virtual bool OnDrawingContent (DrawContext? context) => false;
  499. /// <summary>Raised when the View's content is to be drawn.</summary>
  500. /// <remarks>
  501. /// <para>
  502. /// Subscribe to this event to draw custom content for the View. Use the drawing methods available on
  503. /// <see cref="View"/>
  504. /// such as <see cref="View.AddRune(int, int, Rune)"/>, <see cref="View.AddStr(string)"/>, and
  505. /// <see cref="View.FillRect(Rectangle, Rune)"/>.
  506. /// </para>
  507. /// <para>
  508. /// The event is invoked after <see cref="ClearingViewport"/> and <see cref="Text"/> have been drawn, but after
  509. /// <see cref="SubViews"/> have been drawn.
  510. /// </para>
  511. /// <para>
  512. /// <b>Transparency Support:</b> If the View has <see cref="ViewportSettings"/> with
  513. /// <see cref="ViewportSettingsFlags.Transparent"/>
  514. /// set, use the <see cref="DrawEventArgs.DrawContext"/> to report which areas were actually drawn. This enables
  515. /// proper transparency
  516. /// by excluding only the drawn areas from the clip region. See <see cref="DrawContext"/> for details on reporting
  517. /// drawn regions.
  518. /// </para>
  519. /// <para>
  520. /// The <see cref="DrawEventArgs.NewViewport"/> property provides the view-relative rectangle describing the
  521. /// currently visible viewport into the View.
  522. /// </para>
  523. /// </remarks>
  524. public event EventHandler<DrawEventArgs>? DrawingContent;
  525. #endregion DrawContent
  526. #region DrawSubViews
  527. private void DoDrawSubViews (DrawContext? context = null)
  528. {
  529. if (!NeedsDraw || OnDrawingSubViews (context))
  530. {
  531. return;
  532. }
  533. // TODO: Get rid of this vf in lieu of the one above
  534. if (OnDrawingSubViews ())
  535. {
  536. return;
  537. }
  538. var dev = new DrawEventArgs (Viewport, Rectangle.Empty, context);
  539. DrawingSubViews?.Invoke (this, dev);
  540. if (dev.Cancel)
  541. {
  542. return;
  543. }
  544. if (!SubViewNeedsDraw)
  545. {
  546. return;
  547. }
  548. DrawSubViews (context);
  549. }
  550. /// <summary>
  551. /// Called when the <see cref="SubViews"/> are to be drawn.
  552. /// </summary>
  553. /// <param name="context">The draw context to report drawn areas to, or null if not tracking.</param>
  554. /// <returns><see langword="true"/> to stop further drawing of <see cref="SubViews"/>.</returns>
  555. protected virtual bool OnDrawingSubViews (DrawContext? context) => false;
  556. /// <summary>
  557. /// Called when the <see cref="SubViews"/> are to be drawn.
  558. /// </summary>
  559. /// <returns><see langword="true"/> to stop further drawing of <see cref="SubViews"/>.</returns>
  560. protected virtual bool OnDrawingSubViews () => false;
  561. /// <summary>Raised when the <see cref="SubViews"/> are to be drawn.</summary>
  562. /// <remarks>
  563. /// </remarks>
  564. /// <returns>
  565. /// Set <see cref="CancelEventArgs.Cancel"/> to <see langword="true"/> to stop further drawing of
  566. /// <see cref="SubViews"/>.
  567. /// </returns>
  568. public event EventHandler<DrawEventArgs>? DrawingSubViews;
  569. /// <summary>
  570. /// Draws the <see cref="SubViews"/>.
  571. /// </summary>
  572. /// <param name="context">The draw context to report drawn areas to, or null if not tracking.</param>
  573. public void DrawSubViews (DrawContext? context = null)
  574. {
  575. if (InternalSubViews.Count == 0)
  576. {
  577. return;
  578. }
  579. // Draw the SubViews in reverse Z-order to leverage clipping.
  580. // SubViews earlier in the collection are drawn last (on top).
  581. foreach (View view in InternalSubViews.Snapshot ().Where (v => v.Visible).Reverse ())
  582. {
  583. // TODO: HACK - This forcing of SetNeedsDraw with SuperViewRendersLineCanvas enables auto line join to work, but is brute force.
  584. if (view.SuperViewRendersLineCanvas || view.ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
  585. {
  586. //view.SetNeedsDraw ();
  587. }
  588. view.Draw (context);
  589. if (view.SuperViewRendersLineCanvas)
  590. {
  591. LineCanvas.Merge (view.LineCanvas);
  592. view.LineCanvas.Clear ();
  593. }
  594. }
  595. }
  596. #endregion DrawSubViews
  597. #region DrawLineCanvas
  598. private void DoRenderLineCanvas (DrawContext? context)
  599. {
  600. // TODO: Add context to OnRenderingLineCanvas
  601. if (!NeedsDraw || OnRenderingLineCanvas ())
  602. {
  603. return;
  604. }
  605. // TODO: Add event
  606. RenderLineCanvas (context);
  607. }
  608. /// <summary>
  609. /// Called when the <see cref="View.LineCanvas"/> is to be rendered. See <see cref="RenderLineCanvas"/>.
  610. /// </summary>
  611. /// <returns><see langword="true"/> to stop further drawing of <see cref="LineCanvas"/>.</returns>
  612. protected virtual bool OnRenderingLineCanvas () => false;
  613. /// <summary>The canvas that any line drawing that is to be shared by subviews of this view should add lines to.</summary>
  614. /// <remarks><see cref="Border"/> adds lines to this LineCanvas.</remarks>
  615. public LineCanvas LineCanvas { get; } = new ();
  616. /// <summary>
  617. /// Gets or sets whether this View will use its SuperView's <see cref="LineCanvas"/> for rendering any
  618. /// lines. If <see langword="true"/> the rendering of any borders drawn by this view will be done by its
  619. /// SuperView. If <see langword="false"/> (the default) this View's <see cref="OnDrawingAdornments"/> method will
  620. /// be called to render the borders.
  621. /// </summary>
  622. public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
  623. /// <summary>
  624. /// Causes the contents of <see cref="LineCanvas"/> to be drawn.
  625. /// If <see cref="SuperViewRendersLineCanvas"/> is true, only the
  626. /// <see cref="LineCanvas"/> of this view's SubViews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is
  627. /// false (the default), this method will cause the <see cref="LineCanvas"/> to be rendered.
  628. /// </summary>
  629. /// <param name="context"></param>
  630. public void RenderLineCanvas (DrawContext? context)
  631. {
  632. if (Driver is null)
  633. {
  634. return;
  635. }
  636. if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rectangle.Empty)
  637. {
  638. // Get both cell map and Region in a single pass through the canvas
  639. (Dictionary<Point, Cell?> cellMap, Region lineRegion) = LineCanvas.GetCellMapWithRegion ();
  640. foreach (KeyValuePair<Point, Cell?> p in cellMap)
  641. {
  642. // Get the entire map
  643. if (p.Value is { })
  644. {
  645. SetAttribute (p.Value.Value.Attribute ?? GetAttributeForRole (VisualRole.Normal));
  646. Driver.Move (p.Key.X, p.Key.Y);
  647. // TODO: #2616 - Support combining sequences that don't normalize
  648. AddStr (p.Value.Value.Grapheme);
  649. }
  650. }
  651. // Report the drawn region for transparency support
  652. // Region was built during the GetCellMapWithRegion() call above
  653. if (context is { } && cellMap.Count > 0)
  654. {
  655. context.AddDrawnRegion (lineRegion);
  656. }
  657. LineCanvas.Clear ();
  658. }
  659. }
  660. #endregion DrawLineCanvas
  661. #region DrawComplete
  662. /// <summary>
  663. /// Called at the end of <see cref="Draw(DrawContext)"/> to finalize drawing and update the clip region.
  664. /// </summary>
  665. /// <param name="context">
  666. /// The <see cref="DrawContext"/> tracking what regions were drawn by this view and its subviews.
  667. /// May be <see langword="null"/> if not tracking drawn regions.
  668. /// </param>
  669. private void DoDrawComplete (DrawContext? context)
  670. {
  671. // Phase 1: Notify that drawing is complete
  672. // Raise virtual method first, then event. This allows subclasses to override behavior
  673. // before subscribers see the event.
  674. OnDrawComplete (context);
  675. DrawComplete?.Invoke (this, new (Viewport, Viewport, context));
  676. // Phase 2: Update Driver.Clip to exclude this view's drawn area
  677. // This prevents views "behind" this one (earlier in draw order/Z-order) from drawing over it.
  678. // Adornments (Margin, Border, Padding) are handled by their Adornment.Parent view and don't exclude themselves.
  679. if (this is not Adornment)
  680. {
  681. if (ViewportSettings.HasFlag (ViewportSettingsFlags.Transparent))
  682. {
  683. // Transparent View Path:
  684. // Only exclude the regions that were actually drawn, allowing views beneath
  685. // to show through in areas where nothing was drawn.
  686. // The context.DrawnRegion may include areas outside the Viewport (e.g., if content
  687. // was drawn with ViewportSettingsFlags.AllowContentOutsideViewport). We need to clip
  688. // it to the Viewport bounds to prevent excluding areas that aren't visible.
  689. context!.ClipDrawnRegion (ViewportToScreen (Viewport));
  690. // Exclude the actually-drawn region from Driver.Clip
  691. ExcludeFromClip (context.GetDrawnRegion ());
  692. // Border and Padding are always opaque (they draw lines/fills), so exclude them too
  693. ExcludeFromClip (Border?.Thickness.AsRegion (Border.FrameToScreen ()));
  694. ExcludeFromClip (Padding?.Thickness.AsRegion (Padding.FrameToScreen ()));
  695. }
  696. else
  697. {
  698. // Opaque View Path (default):
  699. // Exclude the entire view area from Driver.Clip. This is the typical case where
  700. // the view is considered fully opaque.
  701. // Start with the Frame in screen coordinates
  702. Rectangle borderFrame = FrameToScreen ();
  703. // If there's a Border, use its frame instead (includes the border thickness)
  704. if (Border is { })
  705. {
  706. borderFrame = Border.FrameToScreen ();
  707. }
  708. // Exclude this view's entire area (Border inward, but not Margin) from the clip.
  709. // This prevents any view drawn after this one from drawing in this area.
  710. ExcludeFromClip (borderFrame);
  711. // Update the DrawContext to track that we drew this entire rectangle.
  712. // This allows our SuperView (if any) to know what area we occupied,
  713. // which is important for transparency calculations at higher levels.
  714. context?.AddDrawnRectangle (borderFrame);
  715. }
  716. }
  717. // When this method returns, Driver.Clip has been updated to exclude this view's area.
  718. // The next view drawn (earlier in Z-order, typically a peer view or the SuperView) will see
  719. // a clip with "holes" where this view (and any SubViews drawn before it) are located.
  720. }
  721. /// <summary>
  722. /// Called when the View has completed drawing and is about to update the clip region.
  723. /// </summary>
  724. /// <param name="context">
  725. /// The <see cref="DrawContext"/> containing the regions that were drawn by this view and its subviews.
  726. /// May be <see langword="null"/> if not tracking drawn regions.
  727. /// </param>
  728. /// <remarks>
  729. /// <para>
  730. /// This method is called at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
  731. /// (adornments, content, text, subviews, line canvas) has completed but before the view's area
  732. /// is excluded from <see cref="IDriver.Clip"/>.
  733. /// </para>
  734. /// <para>
  735. /// Use this method to:
  736. /// </para>
  737. /// <list type="bullet">
  738. /// <item>
  739. /// <description>Perform any final drawing operations that need to happen after SubViews are drawn</description>
  740. /// </item>
  741. /// <item>
  742. /// <description>Inspect what was drawn via the <paramref name="context"/> parameter</description>
  743. /// </item>
  744. /// <item>
  745. /// <description>Add additional regions to the <paramref name="context"/> if needed</description>
  746. /// </item>
  747. /// </list>
  748. /// <para>
  749. /// <b>Important:</b> At this point, <see cref="IDriver.Clip"/> has been restored to the state
  750. /// it was in when <see cref="Draw(DrawContext)"/> began. After this method returns, the view's
  751. /// area will be excluded from the clip (see <see cref="DoDrawComplete"/> for details).
  752. /// </para>
  753. /// <para>
  754. /// <b>Transparency Support:</b> If <see cref="ViewportSettings"/> includes
  755. /// <see cref="ViewportSettingsFlags.Transparent"/>, the <paramref name="context"/> parameter
  756. /// contains the actual regions that were drawn. You can inspect this to see what areas
  757. /// will be excluded from the clip, and optionally add more regions if needed.
  758. /// </para>
  759. /// </remarks>
  760. /// <seealso cref="DrawComplete"/>
  761. /// <seealso cref="Draw(DrawContext)"/>
  762. /// <seealso cref="DoDrawComplete"/>
  763. protected virtual void OnDrawComplete (DrawContext? context) { }
  764. /// <summary>Raised when the View has completed drawing and is about to update the clip region.</summary>
  765. /// <remarks>
  766. /// <para>
  767. /// This event is raised at the very end of <see cref="Draw(DrawContext)"/>, after all drawing
  768. /// operations have completed but before the view's area is excluded from <see cref="IDriver.Clip"/>.
  769. /// </para>
  770. /// <para>
  771. /// The <see cref="DrawEventArgs.DrawContext"/> property provides information about what regions
  772. /// were drawn by this view and its subviews. This is particularly useful for views with
  773. /// <see cref="ViewportSettingsFlags.Transparent"/> enabled, as it shows exactly which areas
  774. /// will be excluded from the clip.
  775. /// </para>
  776. /// <para>
  777. /// Use this event to:
  778. /// </para>
  779. /// <list type="bullet">
  780. /// <item>
  781. /// <description>Perform any final drawing operations</description>
  782. /// </item>
  783. /// <item>
  784. /// <description>Inspect what was drawn</description>
  785. /// </item>
  786. /// <item>
  787. /// <description>Track drawing statistics or metrics</description>
  788. /// </item>
  789. /// </list>
  790. /// <para>
  791. /// <b>Note:</b> This event fires <i>after</i> <see cref="OnDrawComplete(DrawContext)"/>. If you need
  792. /// to override the behavior, prefer overriding the virtual method in a subclass rather than
  793. /// subscribing to this event.
  794. /// </para>
  795. /// </remarks>
  796. /// <seealso cref="OnDrawComplete(DrawContext)"/>
  797. /// <seealso cref="Draw(DrawContext)"/>
  798. public event EventHandler<DrawEventArgs>? DrawComplete;
  799. #endregion DrawComplete
  800. }