19 KiB
Layout and GuiGraphicsComposition (Core Layout & Responsive Families)
1. Scope & Purpose
This document explains the GacUI layout architecture centered on GuiGraphicsComposition and its classification subclasses, the predefined layout container families, bidirectional constraint propagation, invalidation & recomputation workflow driven by GuiGraphicsHost::Render, extension guidelines, and the responsive ("reactive") composition family (GuiResponsive*). It consolidates prior Q&A insights into a coherent design reference for future maintenance and extension.
2. Core Abstractions
2.1 Base Class: GuiGraphicsComposition
Responsibilities:
- Maintain hierarchy:
children,parent(mutation viaInsertChild,RemoveChild,MoveChild). - Own a single graphics element:
ownedElement(setterSetOwnedElement). - Store presentation & interaction flags:
visible,transparentToMouse. - Store layout configuration:
internalMargin,preferredMinSize,minSizeLimitation. - Cache measurement:
cachedMinSize(set only byLayout_SetCachedMinSize), and arrangement:cachedBounds(set only byLayout_SetCachedBounds). - Propagate host context:
relatedHostRecordviaUpdateRelatedHostRecord. - Provide virtual layout interface:
Layout_CalculateMinSize()(measurement)Layout_CalculateMinClientSizeForParent(Margin parentInternalMargin)(child -> parent contribution)Layout_CalculateBounds(Size parentSize)(arrangement)
- Orchestrate recursive layout passes via helpers:
Layout_UpdateMinSize()(calls overridden min size function then caches and firesCachedMinSizeChanged).Layout_UpdateBounds(Size parentSize)(computes own bounds then recurses to children).Layout_CalculateMinSizeHelper()shared policy used by most containers.
- Invalidation trigger:
InvokeOnCompositionStateChanged(forceRequestRender)sets flags; does not directly perform layout. - Synchronous optional recompute:
ForceCalculateSizeImmediately()(used sparingly, e.g., interactive splitters). - Rendering entry:
Renderconsumes onlycachedBounds(no layout recalculation inside rendering).
2.2 Classification Subclasses
Purpose: Distinguish measurement & arrangement control patterns.
GuiGraphicsComposition_Trivial- Container autonomously computes measurement and bounds using standard algorithm (often via
Layout_CalculateMinSizeHelper). - Examples:
GuiBoundsComposition,GuiTableComposition,GuiStackComposition,GuiFlowComposition,GuiSharedSizeRootComposition(directly or indirectly).
- Container autonomously computes measurement and bounds using standard algorithm (often via
GuiGraphicsComposition_Controlled- Measurement / bounds set externally by parent; overrides
Layout_CalculateMinSize&Layout_CalculateBoundsto simply return cached values (no internal recompute). Parent writes caches using specialized setters (e.g.,GuiCellComposition::Layout_SetCellBounds,GuiStackItemComposition::Layout_SetStackItemBounds,GuiFlowItemComposition::Layout_SetFlowItemBounds). - Typical for helper items:
GuiCellComposition, splitters, stack items, flow items, shared size items.
- Measurement / bounds set externally by parent; overrides
GuiGraphicsComposition_Specialized- Container with custom arrangement while limiting child -> parent enlargement; usually still uses
Layout_CalculateMinSizeHelperbut overridesLayout_CalculateMinClientSizeForParentto{0,0}. - Examples:
GuiSideAlignedComposition,GuiPartialViewComposition,GuiWindowComposition.
- Container with custom arrangement while limiting child -> parent enlargement; usually still uses
3. Layout Pass Orchestration
3.1 Measurement Flow
Layout_UpdateMinSize() executes:
- Calls overridden
Layout_CalculateMinSize(). - That implementation typically delegates to
Layout_CalculateMinSizeHelper():- Start from
preferredMinSize. - If
minSizeLimitation != NoLimit, merge element renderer minimum. - Add
internalMargin. - For each child:
child->Layout_UpdateMinSize(). - If own
minSizeLimitation == LimitToElementAndChildren, incorporate each child's contribution viachild->Layout_CalculateMinClientSizeForParent(internalMargin).
- Start from
- Result stored by
Layout_SetCachedMinSize()raisingCachedMinSizeChangedfor observers (parents, containers, special logic like stack/table invalidation flags).
3.2 Arrangement Flow
Layout_UpdateBounds(parentSize) executes:
- Compute own rectangle with overridden
Layout_CalculateBounds(parentSize), store viaLayout_SetCachedBounds. - For each child: call
child->Layout_UpdateBounds(ownSize). - Controlled children rely on parent precomputed rectangles delivered via container-specific setters instead of independent calculation.
3.3 Host-Driven Iteration
- Changes call
InvokeOnCompositionStateChanged()marking layout invalid (and optionally requesting a render). GuiGraphicsHost::Renderloops: if any layout-invalid flag is set for the window root subtree, perform a full layout (measurement + arrangement cascade), then render. Repeats until no invalidation remains.- Ensures deterministic stabilization without requiring synchronous recalculation inside property setters.
ForceCalculateSizeImmediately()is an optimization for immediate feedback inside event handlers (e.g., splitter dragging) but never required for correctness.
4. Invalidation Sources & Flags
4.1 Generic Triggers
- Structural:
InsertChild,RemoveChild,MoveChild. - Property mutations:
SetInternalMargin,SetPreferredMinSize,SetVisible,SetMinSizeLimitation, alignment & anchor changes. - Element replacement:
SetOwnedElement.
4.2 Container-Specific Triggers
- Table:
SetRowOption,SetColumnOption,SetCellPadding,SetBorderVisible,SetRowsAndColumns(setlayout_invalid,layout_invalidCellBounds). - Stack:
SetDirection,SetPadding, scroll / ensure visible logic (setslayout_invalid). - Flow:
SetAxis,SetRowPadding, other adjustments settinglayout_invalid. - SharedSizeRoot: any group membership changes or measuring resets dictionaries.
- Splitters: mouse move adjusting adjacent option values then invalidating parent + optional immediate recompute.
- Responsive (later section): level transitions call
InvokeOnCompositionStateChangedat root responsive node.
4.3 Child-Originated Propagation
- Container listens to children
CachedMinSizeChangedto raise its own invalidation flags (e.g., table cell, stack item, flow item compositions) to recompute aggregated geometry lazily on next layout pass.
5. Bidirectional Constraints
5.1 Parent -> Child Constraint
- Parent supplies available size during
Layout_UpdateBoundsrecursion. - Specialized containers compute per-child rectangles first (table grid, stack linear offsets, flow line wrapping) then assign them (especially controlled children) using internal
Layout_Set...Boundshelpers.
5.2 Child -> Parent Constraint
- During parent measurement, after calling each child's
Layout_UpdateMinSize, parent optionally addschild->Layout_CalculateMinClientSizeForParent(internalMargin)to its accumulated client size if ownminSizeLimitation == LimitToElementAndChildren. - Controlled children often contribute
{0,0}because parent already manually accounts for them (e.g., table computing row/column sizes; stack computing cumulative lengths; flow computing wrapping layout).
6. Predefined Layout Families
Distinct algorithms (excluding helper-only controlled items):
- Bounds:
GuiBoundsComposition(absolute rectangle +alignmentToParent). - Table:
GuiTableComposition(mixed absolute / percentage / min, spanning, splitters viaGuiRowSplitterComposition,GuiColumnSplitterComposition). - Stack:
GuiStackComposition(directional linear, reversed variants, ensure-visible virtualization support via internal offset logic). - Flow:
GuiFlowComposition(adaptive multi-row wrapping withFlowAlignment&GuiFlowOptionbaseline/alignment choices). - Shared Size:
GuiSharedSizeRootComposition(cross-subtree size normalization for named groups via per-dimension dictionaries; item:GuiSharedSizeItemComposition). - Side Aligned:
GuiSideAlignedComposition(dock to a specified side with length ratio / max constraints). - Partial View:
GuiPartialViewComposition(scroll / viewport style fractional sub-rectangle selection with offsets). - Window Root:
GuiWindowComposition(ties client area to native window size, root for layout passes). Helper controlled participants:GuiCellComposition,GuiStackItemComposition,GuiFlowItemComposition,GuiSharedSizeItemComposition,GuiRowSplitterComposition,GuiColumnSplitterComposition.
7. Internal Optimization Flags & Lazy Strategies
- Table maintains
layout_invalid,layout_invalidCellBounds, recalculating row/column metrics and cell rectangles only when flagged insideLayout_CalculateMinSize/Layout_CalculateBounds(helpers:Layout_UpdateCellBoundsInternal,Layout_UpdateCellBoundsPercentages,Layout_UpdateCellBoundsOffsets). - Stack uses
layout_invalid; recalculations centralized inLayout_UpdateStackItemMinSizesand arrangement inLayout_UpdateStackItemBounds. - Flow uses
layout_invalidplus last constraint width; layout regeneration inLayout_UpdateFlowItemLayout/Layout_UpdateFlowItemLayoutByConstraint. - Shared Size re-derives
CalculateOriginalMinSizes,CollectSizes,AlignSizeseach pass (group dictionary rebuild). - Responsive container (later) recalculates bounds iteratively while adjusting levels.
8. Responsive (Reactive) Composition Family
8.1 Goals
Add adaptive multi-level representations (compact to expanded) without duplicating control state, layering on top of existing layout passes and invalidation system.
8.2 Key Classes
- Abstract:
GuiResponsiveCompositionBase(inheritsGuiBoundsComposition). - Strategy / aggregator:
GuiResponsiveViewComposition(collection of alternative view subtrees + shared control migration).GuiResponsiveStackComposition(summation-based level aggregation; adjust one child per level change).GuiResponsiveGroupComposition(max-based aggregation; synchronized level propagation across children).GuiResponsiveFixedComposition(terminal level 0 node).GuiResponsiveSharedComposition(placeholder that installs a control from view'ssharedControls).GuiResponsiveContainerComposition(auto-resizes target by dynamic LevelUp/LevelDown based on live size comparisons).
8.3 Level Model & Operations
- Each responsive subtree exposes
GetLevelCount()andGetCurrentLevel(). - Transition methods:
LevelUp(),LevelDown()return bool (true if level changed) enabling parent fallbacks. GuiResponsiveViewCompositionmaintainscurrentViewpointer; switching occurs only when leaf view can't further down/up inside itself (delegation to child fails) then advances to next/previous view; firesBeforeSwitchingViewevent before installing new view child.- Aggregation formulas:
- View:
levelCount= ?(child.levelCount or 1 if direction mismatch);currentLevelcomputed by reverse accumulation. - Stack:
levelCount= ?(children.levelCount - 1) + 1;currentLevel= ?(children.currentLevel); change selects best candidate child by size heuristic. - Group:
levelCount= max(children.levelCount);currentLevel= max(children.currentLevel); change broadcasts attempts. - Fixed: constants (1,0).
- View:
8.4 Direction Filtering
- Each node has
ResponsiveDirection directionmask; only children whose direction overlaps (child->GetDirection()) participate in counts & changes (macro-assisted filtering in Stack/Group code).
8.5 Shared Control Migration
GuiResponsiveSharedCompositionlocates ancestorGuiResponsiveViewCompositioninOnParentLineChangedand installs its designated shared control (validated membership insharedControls).- On view switch: old subtree removed, new view added, placeholders reinstall matching shared controls preserving internal widget state.
8.6 Automatic Adaptive Wrapper
GuiResponsiveContainerCompositionmonitors actual bounds vsminSizeLowerBound/minSizeUpperBound.- If shrinking below lower bound: loop
Layout_AdjustLevelDown(calls targetLevelDown+ remeasure until fits or minimal). - If expanding beyond upper bound: loop
Layout_AdjustLevelUp(speculative raise then rollback if overshoot) usingLayout_CompareSizeto test width/height axes indicated by direction bits. - After each level change, invokes
responsiveTarget->Layout_UpdateMinSize()to refresh bounds used for subsequent comparisons.
8.7 Invalidation & Events
- Any level recalculation culminating at root triggers
InvokeOnCompositionStateChanged(root determined whenresponsiveParent == nullptr). - Events:
LevelCountChanged,CurrentLevelChangedemitted only on value change;BeforeSwitchingViewemitted just prior to replacing the active view child.
8.8 Extension Guidelines (Responsive)
- Subclass
GuiResponsiveCompositionBase. - Implement level introspection and transitions (
GetLevelCount,GetCurrentLevel,LevelUp,LevelDown). - Maintain internal child collections with insertion/removal hooks (
OnResponsiveChildInserted/Removed) if aggregating. - Propagate recalculation upward via
OnResponsiveChildLevelUpdatedafter recomputing own counts. - Apply direction filtering semantics uniformly (mask intersection tests).
9. Detailed Constraint Examples
9.1 Table Example (Column Percentage Change)
- Setter
GuiTableComposition::SetColumnOptionupdates model then callsInvokeOnCompositionStateChanged(). - Next
GuiGraphicsHost::Renderdetects invalid layout, triggers full window layout. - Table
Layout_CalculateMinSizeseeslayout_invalid-> recomputes metrics;Layout_CalculateBoundsupdates eachGuiCellCompositionviaLayout_SetCellBounds. - Rendering consumes new
cachedBounds.
9.2 Interactive Splitter Drag
- Mouse move updates adjacent Absolute options.
- Each option setter invalidates table.
- Drag handler optionally calls
ForceCalculateSizeImmediately()for immediate geometry update (without waiting for host loop) to achieve fluid cursor feedback.
10. Adding a New Layout Composition (Non-Responsive)
Checklist:
- Choose subclass archetype (Trivial vs Specialized vs Controlled) based on whether parent or child owns measurement/arrangement logic.
- Define internal dirty flags mirroring existing patterns (e.g.,
layout_invalid). - Implement measurement using either
Layout_CalculateMinSizeHelper()or custom pass collecting controlled child min sizes first (as table/stack/flow do). - Implement arrangement; for multi-phase (size distribution + placement) separate helpers and guard with dirty flags.
- Decide child -> parent influence by overriding
Layout_CalculateMinClientSizeForParent(return{0,0}to suppress growth). - Provide internal setters for controlled children to assign bounds then mark them (e.g.,
Layout_SetXItemBounds). - Fire
InvokeOnCompositionStateChanged()in every state-mutating setter. - Subscribe to children
CachedMinSizeChangedwhere dynamic re-aggregation is required. - Use
ForceCalculateSizeImmediately()only for real-time adjustments inside input handlers when latency matters.
11. Adding a New Responsive Strategy
Steps:
- Subclass
GuiResponsiveCompositionBase. - Maintain children list & direction mask; filter by direction overlaps.
- Implement
GetLevelCount/GetCurrentLevelwith chosen aggregation formula. - Implement
LevelDown/LevelUpreturning false when already at boundary (enabling parent fallback switching logic in view/stack/group containers). - Invoke recalculation & root invalidation through
OnResponsiveChildLevelUpdated. - Integrate with container adaptation if needed (work with
GuiResponsiveContainerComposition). - Provide deterministic selection heuristics for partial adjustments (e.g., choose largest or smallest child) if not all children change simultaneously.
12. Separation of Concerns
- Layout computation (measurement + arrangement) is strictly segregated from rendering (
Renderside-effect-free wrt layout state). - Invalidation is single entry (
InvokeOnCompositionStateChanged) leaving scheduling & iteration to the host. - Responsive system layers on top without altering the base pass algorithm; it only manipulates subtree structure and min-size outputs between passes.
13. Reliability & Stability Patterns
- Iterative host loop ensures eventual fixpoint when cascading invalidations occur (e.g., responsive level changes causing new size measurements).
- Dirty flag + lazy recomputation prevents redundant heavy recomputations (especially table & flow geometry).
- Controlled compositions decouple per-item geometry from measuring logic to simplify parent algorithms.
14. Proof Reference Index (Quick Lookup)
- Invalidation Core:
GuiGraphicsComposition::InvokeOnCompositionStateChanged. - Layout Core Helpers:
Layout_CalculateMinSizeHelper,Layout_UpdateMinSize,Layout_UpdateBounds. - Forced Immediate Recompute:
ForceCalculateSizeImmediately. - Table:
GuiTableComposition::{SetRowOption,SetColumnOption,Layout_CalculateMinSize,Layout_UpdateCellBoundsInternal,Layout_UpdateCellBoundsPercentages,Layout_UpdateCellBoundsOffsets}, cell bounds setterGuiCellComposition::Layout_SetCellBounds. - Stack:
GuiStackComposition::{SetDirection,Layout_UpdateStackItemMinSizes,Layout_CalculateMinSize,Layout_UpdateStackItemBounds}, item setterGuiStackItemComposition::Layout_SetStackItemBounds. - Flow:
GuiFlowComposition::{SetAxis,SetRowPadding,Layout_UpdateFlowItemLayout,Layout_UpdateFlowItemLayoutByConstraint}, item setterGuiFlowItemComposition::Layout_SetFlowItemBounds. - Shared Size:
GuiSharedSizeRootComposition::{CalculateOriginalMinSizes,CollectSizes,AlignSizes}, item logicGuiSharedSizeItemComposition::Layout_CalculateMinSize. - Splitters:
GuiTableSplitterCompositionBase::OnMouseMoveHelper. - Responsive Base:
GuiResponsiveCompositionBase::{OnParentLineChanged,OnResponsiveChildLevelUpdated}. - Responsive View:
GuiResponsiveViewComposition::{LevelDown,LevelUp,CalculateLevelCount,CalculateCurrentLevel}. - Responsive Stack:
GuiResponsiveStackComposition::{CalculateLevelCount,CalculateCurrentLevel,LevelDown,LevelUp}. - Responsive Group:
GuiResponsiveGroupComposition::{CalculateLevelCount,CalculateCurrentLevel,LevelDown,LevelUp}. - Responsive Fixed:
GuiResponsiveFixedCompositiontrivial implementations. - Responsive Shared:
GuiResponsiveSharedComposition::{OnParentLineChanged,SetSharedControl}. - Responsive Container:
GuiResponsiveContainerComposition::{Layout_CalculateBounds,Layout_AdjustLevelUp,Layout_AdjustLevelDown,Layout_CompareSize}.
15. Summary
The GacUI layout engine employs a clean separation between invalidation, measurement, arrangement, and rendering, with composition classification enabling optimized parent-child cooperation patterns. Predefined container families cover common layout paradigms (grid, linear, wrapping, docking, viewport, shared sizing) while the responsive layer introduces adaptive multi-level UI transformation without complicating the core pass. Extension points are well-localized: new algorithms implement three virtual layout functions (or override contribution behavior), while responsive strategies implement discrete level semantics. Lazy flags and controlled item abstractions keep recomputation efficient, and host-driven iterative stabilization guarantees correctness even under cascading dynamic changes.