Files
GacUI/.github/KnowledgeBase/KB_GacUI_Design_LayoutAndGuiGraphicsComposition.md
2025-10-30 22:39:15 -07:00

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 via InsertChild, RemoveChild, MoveChild).
  • Own a single graphics element: ownedElement (setter SetOwnedElement).
  • Store presentation & interaction flags: visible, transparentToMouse.
  • Store layout configuration: internalMargin, preferredMinSize, minSizeLimitation.
  • Cache measurement: cachedMinSize (set only by Layout_SetCachedMinSize), and arrangement: cachedBounds (set only by Layout_SetCachedBounds).
  • Propagate host context: relatedHostRecord via UpdateRelatedHostRecord.
  • 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 fires CachedMinSizeChanged).
    • 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: Render consumes only cachedBounds (no layout recalculation inside rendering).

2.2 Classification Subclasses

Purpose: Distinguish measurement & arrangement control patterns.

  1. GuiGraphicsComposition_Trivial
    • Container autonomously computes measurement and bounds using standard algorithm (often via Layout_CalculateMinSizeHelper).
    • Examples: GuiBoundsComposition, GuiTableComposition, GuiStackComposition, GuiFlowComposition, GuiSharedSizeRootComposition (directly or indirectly).
  2. GuiGraphicsComposition_Controlled
    • Measurement / bounds set externally by parent; overrides Layout_CalculateMinSize & Layout_CalculateBounds to 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.
  3. GuiGraphicsComposition_Specialized
    • Container with custom arrangement while limiting child -> parent enlargement; usually still uses Layout_CalculateMinSizeHelper but overrides Layout_CalculateMinClientSizeForParent to {0,0}.
    • Examples: GuiSideAlignedComposition, GuiPartialViewComposition, GuiWindowComposition.

3. Layout Pass Orchestration

3.1 Measurement Flow

Layout_UpdateMinSize() executes:

  1. Calls overridden Layout_CalculateMinSize().
  2. 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 via child->Layout_CalculateMinClientSizeForParent(internalMargin).
  3. Result stored by Layout_SetCachedMinSize() raising CachedMinSizeChanged for observers (parents, containers, special logic like stack/table invalidation flags).

3.2 Arrangement Flow

Layout_UpdateBounds(parentSize) executes:

  1. Compute own rectangle with overridden Layout_CalculateBounds(parentSize), store via Layout_SetCachedBounds.
  2. For each child: call child->Layout_UpdateBounds(ownSize).
  3. 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::Render loops: 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 (set layout_invalid, layout_invalidCellBounds).
  • Stack: SetDirection, SetPadding, scroll / ensure visible logic (sets layout_invalid).
  • Flow: SetAxis, SetRowPadding, other adjustments setting layout_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 InvokeOnCompositionStateChanged at root responsive node.

4.3 Child-Originated Propagation

  • Container listens to children CachedMinSizeChanged to 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_UpdateBounds recursion.
  • 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...Bounds helpers.

5.2 Child -> Parent Constraint

  • During parent measurement, after calling each child's Layout_UpdateMinSize, parent optionally adds child->Layout_CalculateMinClientSizeForParent(internalMargin) to its accumulated client size if own minSizeLimitation == 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):

  1. Bounds: GuiBoundsComposition (absolute rectangle + alignmentToParent).
  2. Table: GuiTableComposition (mixed absolute / percentage / min, spanning, splitters via GuiRowSplitterComposition, GuiColumnSplitterComposition).
  3. Stack: GuiStackComposition (directional linear, reversed variants, ensure-visible virtualization support via internal offset logic).
  4. Flow: GuiFlowComposition (adaptive multi-row wrapping with FlowAlignment & GuiFlowOption baseline/alignment choices).
  5. Shared Size: GuiSharedSizeRootComposition (cross-subtree size normalization for named groups via per-dimension dictionaries; item: GuiSharedSizeItemComposition).
  6. Side Aligned: GuiSideAlignedComposition (dock to a specified side with length ratio / max constraints).
  7. Partial View: GuiPartialViewComposition (scroll / viewport style fractional sub-rectangle selection with offsets).
  8. 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 inside Layout_CalculateMinSize / Layout_CalculateBounds (helpers: Layout_UpdateCellBoundsInternal, Layout_UpdateCellBoundsPercentages, Layout_UpdateCellBoundsOffsets).
  • Stack uses layout_invalid; recalculations centralized in Layout_UpdateStackItemMinSizes and arrangement in Layout_UpdateStackItemBounds.
  • Flow uses layout_invalid plus last constraint width; layout regeneration in Layout_UpdateFlowItemLayout / Layout_UpdateFlowItemLayoutByConstraint.
  • Shared Size re-derives CalculateOriginalMinSizes, CollectSizes, AlignSizes each 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 (inherits GuiBoundsComposition).
  • 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's sharedControls).
    • GuiResponsiveContainerComposition (auto-resizes target by dynamic LevelUp/LevelDown based on live size comparisons).

8.3 Level Model & Operations

  • Each responsive subtree exposes GetLevelCount() and GetCurrentLevel().
  • Transition methods: LevelUp(), LevelDown() return bool (true if level changed) enabling parent fallbacks.
  • GuiResponsiveViewComposition maintains currentView pointer; switching occurs only when leaf view can't further down/up inside itself (delegation to child fails) then advances to next/previous view; fires BeforeSwitchingView event before installing new view child.
  • Aggregation formulas:
    • View: levelCount = ?(child.levelCount or 1 if direction mismatch); currentLevel computed 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).

8.4 Direction Filtering

  • Each node has ResponsiveDirection direction mask; only children whose direction overlaps (child->GetDirection()) participate in counts & changes (macro-assisted filtering in Stack/Group code).

8.5 Shared Control Migration

  • GuiResponsiveSharedComposition locates ancestor GuiResponsiveViewComposition in OnParentLineChanged and installs its designated shared control (validated membership in sharedControls).
  • On view switch: old subtree removed, new view added, placeholders reinstall matching shared controls preserving internal widget state.

8.6 Automatic Adaptive Wrapper

  • GuiResponsiveContainerComposition monitors actual bounds vs minSizeLowerBound / minSizeUpperBound.
  • If shrinking below lower bound: loop Layout_AdjustLevelDown (calls target LevelDown + remeasure until fits or minimal).
  • If expanding beyond upper bound: loop Layout_AdjustLevelUp (speculative raise then rollback if overshoot) using Layout_CompareSize to 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 when responsiveParent == nullptr).
  • Events: LevelCountChanged, CurrentLevelChanged emitted only on value change; BeforeSwitchingView emitted 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 OnResponsiveChildLevelUpdated after recomputing own counts.
  • Apply direction filtering semantics uniformly (mask intersection tests).

9. Detailed Constraint Examples

9.1 Table Example (Column Percentage Change)

  1. Setter GuiTableComposition::SetColumnOption updates model then calls InvokeOnCompositionStateChanged().
  2. Next GuiGraphicsHost::Render detects invalid layout, triggers full window layout.
  3. Table Layout_CalculateMinSize sees layout_invalid -> recomputes metrics; Layout_CalculateBounds updates each GuiCellComposition via Layout_SetCellBounds.
  4. Rendering consumes new cachedBounds.

9.2 Interactive Splitter Drag

  1. Mouse move updates adjacent Absolute options.
  2. Each option setter invalidates table.
  3. 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:

  1. Choose subclass archetype (Trivial vs Specialized vs Controlled) based on whether parent or child owns measurement/arrangement logic.
  2. Define internal dirty flags mirroring existing patterns (e.g., layout_invalid).
  3. Implement measurement using either Layout_CalculateMinSizeHelper() or custom pass collecting controlled child min sizes first (as table/stack/flow do).
  4. Implement arrangement; for multi-phase (size distribution + placement) separate helpers and guard with dirty flags.
  5. Decide child -> parent influence by overriding Layout_CalculateMinClientSizeForParent (return {0,0} to suppress growth).
  6. Provide internal setters for controlled children to assign bounds then mark them (e.g., Layout_SetXItemBounds).
  7. Fire InvokeOnCompositionStateChanged() in every state-mutating setter.
  8. Subscribe to children CachedMinSizeChanged where dynamic re-aggregation is required.
  9. Use ForceCalculateSizeImmediately() only for real-time adjustments inside input handlers when latency matters.

11. Adding a New Responsive Strategy

Steps:

  1. Subclass GuiResponsiveCompositionBase.
  2. Maintain children list & direction mask; filter by direction overlaps.
  3. Implement GetLevelCount / GetCurrentLevel with chosen aggregation formula.
  4. Implement LevelDown / LevelUp returning false when already at boundary (enabling parent fallback switching logic in view/stack/group containers).
  5. Invoke recalculation & root invalidation through OnResponsiveChildLevelUpdated.
  6. Integrate with container adaptation if needed (work with GuiResponsiveContainerComposition).
  7. 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 (Render side-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 setter GuiCellComposition::Layout_SetCellBounds.
  • Stack: GuiStackComposition::{SetDirection,Layout_UpdateStackItemMinSizes,Layout_CalculateMinSize,Layout_UpdateStackItemBounds}, item setter GuiStackItemComposition::Layout_SetStackItemBounds.
  • Flow: GuiFlowComposition::{SetAxis,SetRowPadding,Layout_UpdateFlowItemLayout,Layout_UpdateFlowItemLayoutByConstraint}, item setter GuiFlowItemComposition::Layout_SetFlowItemBounds.
  • Shared Size: GuiSharedSizeRootComposition::{CalculateOriginalMinSizes,CollectSizes,AlignSizes}, item logic GuiSharedSizeItemComposition::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: GuiResponsiveFixedComposition trivial 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.