Skip to content

Kh/5498 width height event contract#5577

Merged
harder merged 2 commits into
tui-cs:developfrom
harder:kh/5498-width-height-event-contract
Jul 7, 2026
Merged

Kh/5498 width height event contract#5577
harder merged 2 commits into
tui-cs:developfrom
harder:kh/5498-width-height-event-contract

Conversation

@harder

@harder harder commented Jul 4, 2026

Copy link
Copy Markdown
Member

Fixes #5498 — Document the Width/Height vs Frame event contract

Background

#5498 flagged that frame/layout-driven size changes bypass the Width/Height Changing/Changed events and might leave TextFormatter.ConstrainTo* stale.

The issue asked for a decision:

  1. Raise Width/Height events from the Frame/layout paths.
  2. Document that those events only apply to declarative Dim assignment, and use FrameChanged for resolved-size changes.

This PR takes option 2: document the contract and lock the current behavior with tests.

Findings

The TextFormatter concern is no longer a live bug. SetRelativeLayout now recomputes formatter constraints through SetTextFormatterSize () and FinalizeTextFormatterConstraints (). Direct Frame assignment also flows through that path via Layout (), so the old // BUGBUG: - Reset TextFormatter comments were stale.

The event behavior is intentional once the model is separated into:

  • Declarative layout: Width/Height as Dim
  • Resolved layout: Frame as an absolute Rectangle

A layout pass can change Frame.Width while Width remains Dim.Fill (). Raising WidthChanged in that case would imply the declarative Dim changed, which is not true. Direct Frame assignment does overwrite the internal Dim fields with absolute values, but that is bookkeeping after the Frame has already changed, not a cancellable declarative Width/Height assignment.

Contract:
WidthChanging/WidthChanged and HeightChanging/HeightChanged observe declarative Dim assignment only. FrameChanged is the event for resolved position/size changes from any cause.

Changes

  • Replaced the stale BUGBUG blocks in View.Layout.cs with design notes explaining the direct field writes.
  • Added XML docs to Width, Height, and FrameChanged describing the event contract.
  • Added contract tests covering:
    • WidthChanged/HeightChanged do not fire from direct Frame assignment.
    • WidthChanging/HeightChanging do not fire from direct Frame assignment.
    • OnWidthChanging/OnWidthChanged and OnHeightChanging/OnHeightChanged are not invoked from direct Frame assignment.
    • The same events/hooks do not fire from layout-driven resize.
    • FrameChanged does fire from direct Frame assignment, layout-driven resize, and declarative assignment after layout.

Notes

FrameChanged fires after Frame has been updated. It should be used to observe the resolved Frame, not as a signal that every secondary layout/formatter side effect in the caller path has completed.

Testing

  • dotnet test --project Tests/UnitTestsParallelizable --filter-class "*ViewLayoutEventTests"
    • Passed: 21/21
  • dotnet build Terminal.Gui/Terminal.Gui.csproj --no-restore -t:Rebuild -v:minimal
    • Passed with one existing CS0419 XML-doc warning in View.Drawing.cs
  • git diff --check upstream/develop...HEAD
    • Passed

Actual changes are comments/XML docs only; behavior is unchanged.

harder and others added 2 commits July 4, 2026 15:23
tui-cs#5498 (split off from tui-cs#4522) flagged that Frame/layout-driven size changes
bypass the Width/Height change events and skip the TextFormatter reset.

Re-evaluated after the related layout/TextFormatter perf work landed
(tui-cs#4522, tui-cs#5357, tui-cs#5358/tui-cs#5359, tui-cs#5499, formatter-constraint fixes):

- TextFormatter reset is now a non-issue. SetRelativeLayout recomputes the
  constraints via SetTextFormatterSize () + FinalizeTextFormatterConstraints (),
  and the Frame setter flows through the same path via Layout (). The
  "Reset TextFormatter" BUGBUG bullets were stale.

- The Width/Height events "gap" is actually the correct design once declarative
  (Width/Height = Dim) is separated from resolved (Frame = absolute Rectangle).
  A layout pass changes the Frame, not the Dim (Dim.Fill () stays Dim.Fill ()),
  and the Frame setter's _width = absolute is bookkeeping, not a user-initiated
  Width change. FrameChanged already covers resolved-size changes from any cause.

Changes (no behavior change):
- Replace the two stale BUGBUG blocks in View.Layout.cs (Frame setter +
  SetRelativeLayout) with accurate design notes.
- Document the contract on Width/Height (declarative-assignment only) and on
  FrameChanged (canonical resolved-size event).
- Add ViewLayoutEventTests locking the contract: Width/HeightChanged do NOT
  fire from the Frame setter or a layout-driven resize; FrameChanged DOES fire
  in those cases and on declarative assignment after layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

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

Documents and tests the intended event-semantics contract between declarative Width/Height (Dim) changes and resolved Frame changes in View, clarifying that layout/Frame-driven size updates should be observed via FrameChanged (Fixes #5498).

Changes:

  • Replaces stale BUGBUG notes in View.Layout.cs with design notes explaining why internal Pos/Dim fields are written directly and why Width/Height change events are not raised from Frame/layout paths.
  • Adds XML documentation to FrameChanged, Width, and Height describing the declarative-vs-resolved event contract.
  • Adds unit tests that lock in the contract for direct Frame assignment and layout-driven resizing.

Reviewed changes

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

File Description
Tests/UnitTestsParallelizable/ViewBase/Layout/ViewLayoutEventTests.cs Adds contract tests ensuring Width/Height Changing/Changed (and hooks) don’t fire for Frame/layout changes, while FrameChanged does.
Terminal.Gui/ViewBase/View.Layout.cs Updates inline comments and XML docs to formally document the Width/Height vs FrameChanged event contract.

@harder harder marked this pull request as ready for review July 5, 2026 20:41
@harder harder requested a review from tig as a code owner July 5, 2026 20:41

@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.

Thank you. This is long overdue and the right fix.

@harder harder merged commit c7030e7 into tui-cs:develop Jul 7, 2026
14 checks passed
@harder harder deleted the kh/5498-width-height-event-contract branch July 7, 2026 03: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.

Frame/layout-driven size changes bypass Width/Height change events and TextFormatter reset

3 participants