Fixes #5434. Track adornment thickness deltas so LayoutAndDraw can stop force-drawing#5464
Fixes #5434. Track adornment thickness deltas so LayoutAndDraw can stop force-drawing#5464harder wants to merge 2 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fix indentation of the Margin.ThicknessChanged handler body and factor the duplicated viewport-size formula out of GetViewportForAdornmentThickness and GetViewportFrameForAdornmentThickness into a shared GetViewportSizeForThickness helper. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Follow-up to #5431 that removes the blanket force=true redraw in ApplicationImpl.LayoutAndDraw when layout ran. To make that safe, adornment ThicknessChanged handlers now track the previous thickness so that when an adornment's inner frame changes (without the View's outer Frame changing), the SuperView region is invalidated precisely (mirroring SetFrame's pattern), and ViewportChanged is raised when the viewport actually changed.
Changes:
LayoutAndDrawnow passes onlyforceRedrawtoView.Draw, droppingneededLayoutfrom the force condition.- Margin/Border/Padding
ThicknessChangedhandlers are routed through a newHandleAdornmentThicknessChangedlocal that captures pre/post viewport state, callsInvalidateAdornmentThicknessChangeon the SuperView, and raisesViewportChanged. TabsFanOutIntegrationTestsflipped from documenting fan-out to assertinginactiveTextDraws == 0.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Terminal.Gui/App/ApplicationImpl.Screen.cs | Drops neededLayout from force arg to View.Draw. |
| Terminal.Gui/ViewBase/View.Adornments.cs | Tracks per-adornment previous thickness, invalidates SuperView precisely on viewport-frame change, raises ViewportChanged. |
| Tests/IntegrationTests/TabsFanOutIntegrationTests.cs | Asserts zero inactive-tab fan-out and updates the doc comment. |
tig
left a comment
There was a problem hiding this comment.
Clean and well-scoped. The approach is correct: track previous adornment thickness in closures, compare old vs new viewport frame on change, and invalidate precisely at the SuperView instead of force-drawing the entire tree. Removing neededLayout from the force condition in LayoutAndDraw is the payoff, and the integration test flipping from "documents remaining fan-out" to Assert.Equal(0, inactiveTextDraws) proves it works.
The closure pattern (local function returning the new thickness, reassigned to the captured variable) is a bit unusual but valid and keeps the event wiring concise. The null-SuperView fallback to NeedsClearScreenNextIteration() handles the top-level case correctly.
I did not have time to do a bunch of user testing. If you feel like you've played hard with these Scenarios I'm good: Adornments, Shadow Styles, Menus, Arrangements...
Summary
Follow-up to #5431 (#5358).
ApplicationImpl.LayoutAndDrawpreviously force-redrew theentire runnable tree whenever any view needed layout (
force = neededLayout || forceRedraw),cascading
SetNeedsDrawto all overlapping subviews. That blanket force existed only becauseadornment thickness changes alter rendered coverage without changing the View's
Frame, sonothing invalidated the affected region precisely.
This PR tracks adornment thickness deltas and invalidates the precise SuperView region on
change, allowing the force to be dropped (
force = forceRedraw).Changes
ApplicationImpl.Screen.cs—LayoutAndDrawno longer forces a full redraw just becauselayout ran. It now forces only when explicitly requested (
forceRedraw).View.Adornments.cs— theMargin/Border/PaddingThicknessChangedhandlers now routethrough
HandleAdornmentThicknessChanged, which:changed (
InvalidateAdornmentThicknessChange), mirroring the existingSetFrameinvalidation,and falling back to a full-screen clear for top-level (no SuperView) views, and
ViewportChangedwhen the viewport actually changed.TabsFanOutIntegrationTests— flipped from documenting residual fan-out(
Assert.True(inactiveTextDraws > 0, ...)) to asserting zero fan-out(
Assert.Equal(0, inactiveTextDraws)).This completes a consistent pattern: every mutation that changes rendered coverage without a
redraw request now self-invalidates the SuperView —
Framemove/shrink (SetFrame),Visibletoggle (
Visiblesetter), and now adornment thickness (this PR).ViewportChangednow fires when an adornment'sThicknesschanges the viewport size. Previously,setting
Border/Margin/Padding.Thicknessdirectly changedViewport.Sizesilently; it nowraises
ViewportChanged, consistent withSetFrame(which already raises it on any viewportchange). The cursor-adjustment branch in
RaiseViewportChangedEventis a no-op for this casebecause the viewport location is unchanged (delta == 0), so there are no cursor jumps. Downstream
ViewportChangedsubscribers will now be notified on thickness-driven size changes. This isconsidered a latent-bug fix, but is called out explicitly for reviewer sign-off.
Acceptance criteria
LayoutAndDrawno longer force-redraws solely because layout ranTesting
TabsFanOutIntegrationTests(3) andTabsFanOutDiagnosticTestspass with zero-fan-out assertions.ShadowTests(35),BorderViewTests(78),AdornmentTests(116),BorderTests(9),PaddingTests(8),MarginTests(17),ViewportTests(143).UnitTestsParallelizable(17,300) andUnitTests.NonParallelizable(72, +2 skipped) pass.🤖 Generated with Claude Code