View.Drawing.cs 37 KB

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