Skip to content

Fix scrollbar padding accumulation + DimAuto content size drift (#4522)#4864

Closed
duskfold wants to merge 7 commits intogui-cs:v2_developfrom
duskfold:fix/4522-stale-content-size-capture
Closed

Fix scrollbar padding accumulation + DimAuto content size drift (#4522)#4864
duskfold wants to merge 7 commits intogui-cs:v2_developfrom
duskfold:fix/4522-stale-content-size-capture

Conversation

@duskfold
Copy link
Copy Markdown

Builds on top of @tig's #4863 (stale ContentSize re-read). That fix is correct and necessary, but a second bug remained: repeated maximize/restore cycles caused Dialog content to shrink by 2px per cycle until the content area collapsed.

Root cause (traced with instrumented builds)

Two bugs working together:

1. ScrollBar padding accumulation (View.ScrollBars.cs)

The VisibleChanged handlers use relative Padding.Thickness +1/-1 adjustments. During layout re-entry (which happens on every resize), ShowHide fires multiple times per visibility change. Each fire adds another +1, and the -1 on hide doesn't fully compensate because the baseline has already shifted.

Trace data showing Padding.Bottom ratcheting before fix:

Cycle 1: Padding.Bottom = 3 -> 6
Cycle 2: Padding.Bottom = 6 -> 8  
Cycle 3: Padding.Bottom = 8 -> 10
Cycle 4: Padding.Bottom = 10 -> 12

Fix: Track whether scrollbar padding has been applied via boolean flags (_verticalScrollBarPaddingApplied / _horizontalScrollBarPaddingApplied). The handlers become idempotent: +1 only fires when transitioning from not-applied to applied, -1 only when transitioning back.

Trace data after fix:

Every cycle: Padding.Bottom = 3 -> 4 -> 3 (stable)

2. _minimumSubViewsSize floor in non-DimAuto dialogs (DialogTResult.cs)

UpdateSizes unconditionally floors ContentSize at _minimumSubViewsSize. For fixed-size dialogs (non-DimAuto), this captures the high-water mark from the maximized state and never shrinks back, inflating ContentSize beyond the actual Viewport on restore.

Fix: Only use _minimumSubViewsSize as a floor for DimAuto dialogs (which need to grow to fit content). Fixed-size dialogs use Viewport size directly.

Tests

Added ScrollBarPaddingAccumulationTests.cs with two tests:

  • Dialog_Resize_Cycles_Do_Not_Accumulate_Padding: 5 maximize/restore cycles, asserts Padding and Viewport are stable
  • ScrollBar_Visibility_Toggle_Is_Idempotent: 10 layout passes, asserts Padding stays constant

All 7 existing StaleContentSizeCaptureTests continue to pass.

How we found it

Instrumented DialogTResult.cs and AdornmentImpl.cs with file-based tracing (Padding.Thickness setter logged every change with full stack traces). The stack traces showed ScrollBar.ShowHide -> ConfigureVerticalScrollBarEvents/ConfigureHorizontalScrollBarEvents -> Padding.Thickness += 1 firing during PaddingView.OnParentFrameChanged on every layout re-entry cycle.

tig and others added 7 commits March 27, 2026 08:51
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 7 tests in StaleContentSizeCaptureTests that exercise the stale
content size capture bug in LayoutSubViews:

- Test 1: Core stale capture via OnSubViewLayout (regression)
- Test 2: Dialog resize scenario (regression)
- Test 3: Dialog OnSubViewLayout divergence (FAILS - proves bug)
- Test 4: NeedsLayout direct set propagation bypass (regression)
- Test 5: SubViewsLaidOut SetContentSize too late (regression)
- Test 6: ListView OnViewportChanged content size (regression)
- Test 7: TableView RefreshContentSize (regression)

Test 3 demonstrates the core divergence: LayoutSubViews captures
contentSize before OnSubViewLayout fires, so Dialog.UpdateSizes()
changes content size from (57,16) to (80,35) but children were
laid out with the stale (57,16) value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Re-read content size after OnSubViewLayout to ensure updates from SetContentSize are reflected before laying out children. Dialog content area now always floors at Viewport size, preventing it from shrinking below visible bounds. Added documentation of the issue and verification steps.
Refactored the Thickness struct for modern C# style and maintainability. Converted methods and properties to expression-bodied members, collapsed multi-line property definitions, and removed unnecessary null-conditional operators. Updated the Draw method for early returns and clearer logic. Ensured code style consistency with project conventions. No functional changes.
…cs#4522)

Two related bugs causing Dialog content to shrink by 2px per
maximize/restore cycle:

1. ScrollBar.VisibleChanged handlers in View.ScrollBars.cs used
   relative Padding.Thickness +1/-1 adjustments. During layout
   re-entry, ShowHide fires multiple times per visibility change,
   compounding the adjustment. Each cycle added +1 to Padding.Right
   (vertical scrollbar) and +1 to Padding.Bottom (horizontal
   scrollbar), permanently inflating adornment thickness.

   Fix: Track whether scrollbar padding has been applied via boolean
   flags and make VisibleChanged handlers idempotent. The +1 is
   only applied when transitioning from not-applied to applied,
   and -1 only when transitioning back.

2. Dialog.UpdateSizes used _minimumSubViewsSize as a floor for
   ContentSize regardless of whether the dialog uses DimAuto.
   For fixed-size dialogs, this floor captures the high-water mark
   from the maximized state and never shrinks, inflating ContentSize
   beyond the actual Viewport on restore.

   Fix: Only use _minimumSubViewsSize as a floor for DimAuto
   dialogs (which need to grow to fit content). Fixed-size dialogs
   use Viewport size directly.

Traced and verified with instrumented builds: Padding.Bottom now
oscillates cleanly (3 -> 4 -> 3) on every cycle instead of
ratcheting (3 -> 6 -> 8 -> 10 -> ...).

Tests: Added ScrollBarPaddingAccumulationTests with two tests
covering the resize-cycle and idempotency scenarios.
@tig
Copy link
Copy Markdown
Member

tig commented Mar 28, 2026

Thanks. I fixed this in #4862.

@tig tig closed this Mar 28, 2026
@duskfold duskfold deleted the fix/4522-stale-content-size-capture branch April 2, 2026 13:42
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.

4 participants