Skip to content

Fixes #5433. Make View.Draw self-draw context flow explicit#5440

Merged
harder merged 2 commits into
tui-cs:developfrom
harder:fix-5433-explicit-self-draw-context
May 29, 2026
Merged

Fixes #5433. Make View.Draw self-draw context flow explicit#5440
harder merged 2 commits into
tui-cs:developfrom
harder:fix-5433-explicit-self-draw-context

Conversation

@harder

@harder harder commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Makes the View.Draw (...) self-draw flow explicit so the _localDrawContext lifecycle is owned in one place instead of being spread across three hand-aligned if (needsDrawSelf) blocks. Pure refactor — no rendering, ordering, or invalidation behavior changes.

Split from #4973; follow-up to #5431 (#5358), which introduced the needsDrawSelf / _localDrawContext mechanism and explicitly deferred this cleanup.

Background

PR #5431 fixed the child-only redraw fan-out from #5358 by capturing needsDrawSelf = NeedsDraw before DoDrawAdornments escalates NeedsDrawRect, then gating the self-content draw on it. That fix is correct, but its implementation spread the per-view _localDrawContext lifecycle across three separate if (needsDrawSelf) blocks in View.Draw (...):

  1. recreate _localDrawContext,
  2. SetAttributeForRole + DoClearViewport,
  3. (after DoDrawSubViews) DoDrawText + DoDrawContent into _localDrawContext, then merge into the shared context.

_localDrawContext is consumed later in DoDrawComplete for TransparentMouse CachedDrawnRegion hit-testing. The subtle invariants — recreate only on self-redraw, preserve across child-only passes, consume in DoDrawComplete — were split across distant blocks, so a future edit could easily reset or split the lifecycle at the wrong point and reintroduce the #5431 review regression (TransparentMouseParent_ChildOnlyDirty_PreservesCachedDrawnRegion).

Changes

  • View.Drawing.cs — new private DrawSelfContent (DrawContext sharedContext) that owns the entire _localDrawContext lifecycle for a self-redraw pass: (re)create it, draw Text and Content into it, then merge its region into the shared context. Draw (...) now calls this once, gated on needsDrawSelf, after SubViews.
  • View.Drawing.csDraw (...) no longer creates _localDrawContext alongside the viewport clear. DoClearViewport writes only the shared context, so the local context isn't needed until DrawSelfContent; moving creation there keeps create → populate → merge in one place. The remaining "clear self viewport" block carries a comment explaining that self-draw is intentionally split around DoDrawSubViews to preserve order.
  • View.Drawing.cs_localDrawContext field gains an authoritative <remarks> documenting the lifecycle: recreated only on self-redraw passes (in DrawSelfContent), left untouched on child-only passes so DoDrawComplete keeps a valid CachedDrawnRegion.

Draw order is unchanged: Clear (self) → SubViews → flush _lastClearedViewport → Text/Content (self).

Side effect: removes a warning

Collapsing _localDrawContext's creation and use into one method lets nullable flow analysis prove it non-null, removing the pre-existing CS8602 (Dereference of a possibly null reference) warning at the old context.AddDrawnRegion (_localDrawContext.GetDrawnRegion ()) site.

Testing

  • dotnet build --no-restore0 warnings, 0 errors (and removes one pre-existing warning, per above).
  • dotnet test --project Tests/UnitTestsParallelizable --no-build — 17260 pass / 17 pre-existing skips.
    • Contract/regression coverage stays green: SubViewOnlyRedrawTests (incl. TransparentMouseParent_ChildOnlyDirty_PreservesCachedDrawnRegion), RegionAwareClearViewportTests, NeedsDrawTests.

No new tests: this is a behavior-preserving refactor whose contract is already pinned by the #5431 tests above. Coverage is maintained.

Acceptance criteria mapping

AC Coverage
1. self-draw as one explicit unit DrawSelfContent owns the _localDrawContext create/populate/merge; the clear half is a single documented block whose split from content is explained.
2. recreate _localDrawContext only on self-redraw creation lives inside DrawSelfContent, called only when needsDrawSelf.
3. preserve prior context on child-only passes DrawSelfContent not called when needsDrawSelf is false; field left untouched (documented on the field).
4. draw order unchanged Clear → SubViews → Text → Content preserved.

To pull down this PR locally

git remote add copilot git@github.com:harder/Terminal.Gui.git
git fetch copilot fix-5433-explicit-self-draw-context
git checkout copilot/fix-5433-explicit-self-draw-context

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors View.Draw so the self-draw context lifecycle is centralized and easier to reason about, preserving the redraw behavior introduced by #5431 while reducing future regression risk.

Changes:

  • Moves _localDrawContext creation/use/merge into a new DrawSelfContent helper.
  • Keeps the self-clear phase separate to preserve draw order: clear → SubViews → self text/content.
  • Adds documentation for the _localDrawContext lifecycle and a planning note for #5433.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
Terminal.Gui/ViewBase/View.Drawing.cs Centralizes self-content drawing and _localDrawContext lifecycle while preserving existing draw order and child-only redraw behavior.
plans/5433-explicit-self-draw-context.md Documents the issue, intended refactor, verification expectations, and acceptance criteria mapping.

@harder
harder marked this pull request as ready for review May 29, 2026 17:36
@harder
harder requested a review from tig as a code owner May 29, 2026 17:36
harder and others added 2 commits May 29, 2026 12:38
Extract the self-content draw (Text + Content + local-context merge) into a
private DrawSelfContent helper that owns the full _localDrawContext lifecycle
for a self-redraw pass: create, populate, merge. Move the context creation out
of the viewport-clear block — DoClearViewport only writes the shared context,
so the local context isn't needed until DrawSelfContent, keeping create/populate
/merge in one place.

This replaces three separate `if (needsDrawSelf)` blocks (whose alignment had to
be maintained by hand) with a clear/self split documented as intentional around
DoDrawSubViews, plus an authoritative lifecycle comment on the _localDrawContext
field describing why it is preserved across child-only passes (TransparentMouse
CachedDrawnRegion contract from tui-cs#5431). Behavior-preserving; draw order unchanged.

Side effect: collapsing creation and use into one method lets nullable flow
analysis prove _localDrawContext non-null, removing a CS8602 warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@harder
harder force-pushed the fix-5433-explicit-self-draw-context branch from 98c2bfa to 42fa6c1 Compare May 29, 2026 17:38

@tig tig left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice that collapsing it kills the CS8602 instead of forgiving it. The field remarks documenting “preserve on child-only passes” is the bit that stops someone re-breaking TransparentMouse later.

This is hero work and I love your methodical approach!

@harder
harder merged commit 7c8f9cb into tui-cs:develop May 29, 2026
13 checks passed
@harder
harder deleted the fix-5433-explicit-self-draw-context branch May 29, 2026 18:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Follow up #5431: make View.Draw self-draw context flow explicit

3 participants