Skip to documentation
SLOP

tiny.gui.frame

Reference tiny.gui frame

Defined in tiny.gui.

API (36)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.guiframe
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendereradvancetest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...frameappendSurfaceFrameframe.Sessionappend
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendereradvancetest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...FrameStorageactivateFrameStorageinitprivate sourcelib.trace.src.profiling.storageresetframe.Sessionbegin
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendererdeinittest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...frame.Sessiondeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendererinittest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...frame.Sessioninit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendereradvancetest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...frame.Sessionpublish
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.render.Rendereradvanceframe.SessionstagedWidgets
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.gui.src.frametest: Workspace remains reusable afte...test sourcelib.gui.src.frametest: Workspace retains frame storage...test sourcelib.gui.src.frametest: Workspace warmed builds need no...test sourcelib.gui.src.frametest: frame rects snap to whole pixel...test sourcelib.gui.src.frametest: layers propagate from a subtree...+6 moreprivate sourcelib.gui.src.frame.WorkspaceensureStorageframeappendSurfaceFrameframesurveyFrameRootprivate sourcelib.trace.src.profiling.storageresetframe.WorkspacebuildSurface
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.gui.src.frametest: Workspace remains reusable afte...test sourcelib.gui.src.frametest: Workspace retains frame storage...test sourcelib.gui.src.frametest: Workspace warmed builds need no...test sourcelib.gui.src.frametest: frame rects snap to whole pixel...test sourcelib.gui.src.frametest: layers propagate from a subtree...+6 moreframe.Workspacedeinit
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callstest sourcelib.gui.src.frametest: Workspace remains reusable afte...test sourcelib.gui.src.frametest: Workspace retains frame storage...test sourcelib.gui.src.frametest: Workspace warmed builds need no...test sourcelib.gui.src.frametest: frame rects snap to whole pixel...test sourcelib.gui.src.frametest: layers propagate from a subtree...+6 moreframe.Workspaceinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsframe.Sessionappendframe.WorkspacebuildSurfaceprivate sourcelib.gui.src.frameappendWidgetFramesprivate sourcelib.gui.src.framebuildFlexNodeWithResolversprivate sourcelib.gui.src.framemarkHoveredPointframesnapRectframesurveyFrameRootframeappendSurfaceFrame
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsprivate sourcelib.gui.src.frameappendWidgetFramesframeintersectRectsframechildClip
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linkfun.sdfii.src.engine.system.ui.input.scrolleffectiveprivate; no linkfun.sdfii.src.engine.system.ui.input.scrollkeyboardTargetprivate; no linkfun.sdfii.src.engine.system.ui.input.scrollpointerTargetprivate; no linkfun.sdfii.src.engine.system.ui.renderresolveScrollprivate sourcelib.gui.src.frameresolveScrollframescrollForVisibleTargetframemaxScrollXframemaxScrollYmodelnonNegativeFiniteframeclampScroll
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.gui.src.frameappendWidgetFramesframecontentSize
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.gui.src.frameappendSolvedNodeprivate sourcelib.gui.src.frameappendWidgetFramesframechildClipframeintersectRects
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.gui.src.frameappendSolvedNodeprivate sourcelib.gui.src.frameappendWidgetFramesframemakeTextRef
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.input.scrollkeyboardTargetframeclampScrollframemaxScrollX
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.input.scrollkeyboardTargetframeclampScrollframemaxScrollY
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate; no linkfun.sdfii.src.engine.system.ui.input.eventapplySelectionprivate; no linkfun.sdfii.src.engine.system.ui.renderresolveSelectionprivate sourcelib.gui.src.frameresolveTextSelectionframenormalizeTextSelection
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.gui.src.frameappendSolvedNodeprivate sourcelib.gui.src.frameappendWidgetFramesframeoffsetRect
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linkfun.sdfii.src.engine.system.ui.input.scrollensureVisibleframeclampScrollprivate sourcelib.gui.src.framescrollOffsetForVisibleRangeframescrollForVisibleTarget
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callsprivate sourcelib.gui.src.frameappendSolvedNodeframeappendSurfaceFrameprivate sourcelib.gui.src.frameappendWidgetFramesframesnapRect
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsframe.WorkspacebuildSurfaceframeappendSurfaceFrametest sourcelib.gui.src.frametest: surface frame appends an extern...test sourcelib.gui.src.frametest: surface frame rejects invalid s...framesurveySurfaceprivate sourcelib.gui.src.framevalidateSolvedSubtreesframesurveyFrameRoot
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate; no linkfun.sdfii.src.engine.system.ui.render.RendereradvanceframesurveyFrameRoottest sourcelib.gui.src.frametest: Session publishes double-buffer...test sourcelib.gui.src.frametest: Session steady builds at admitt...test sourcelib.gui.src.frametest: surveySurface counts nodes and ...private sourcelib.gui.src.framesurveyNodeframesurveySurface
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/gui/src/frame.zig

zig
const std = @import("std");const alloc_phase = @import("alloc_phase");const flex_layout = @import("arrange");const model = @import("model.zig");const widget_query = @import("widget.zig");const Allocator = std.mem.Allocator;const RootFrame = model.RootFrame;const UiNode = model.UiNode;const UiSurfaceTree = model.UiSurfaceTree;const UiScroll = model.UiScroll;const UiPoint = model.UiPoint;const UiTextSelection = model.UiTextSelection;const UiFrame = model.UiFrame;const WidgetFrame = model.WidgetFrame;const nonNegativeFinite = model.nonNegativeFinite;pub const max_depth = flex_layout.max_depth;/// Semantic and ancestry data paired with one externally solved rectangle.pub const SolvedBinding = struct {    node: *const UiNode,    /// Preorder span including this binding and all of its descendants.    subtree_size: usize,};/// A caller-owned preorder subtree solved in viewport-local coordinates./// All four spans have equal nonzero length. Clips are already effective./// Offsets translate rectangles only, leaving viewport-local clips anchored.pub const SolvedSubtree = struct {    viewport_widget_id: u64,    bindings: []const SolvedBinding,    rectangles: []const flex_layout.Rect,    clips: []const flex_layout.Rect,    offsets: []const Offset,};pub const SurfaceFrameRoot = struct {    surface: *const UiSurfaceTree,    root_id: u64 = 1,    revision: u64 = 1,    hovered_widget: ?u64 = null,    hovered_point: ?UiPoint = null,    captured_widget: ?u64 = null,    focused_widget: ?u64 = null,    solved_subtrees: []const SolvedSubtree = &.{},};pub const SurfaceFrameOptions = struct {    root_id: u64 = 1,    revision: u64 = 1,    hovered_widget: ?u64 = null,    hovered_point: ?UiPoint = null,    captured_widget: ?u64 = null,    focused_widget: ?u64 = null,    resolvers: FrameResolvers = .{},    solved_subtrees: []const SolvedSubtree = &.{},};pub const FrameResolvers = struct {    context: ?*anyopaque = null,    scroll: ?*const fn (?*anyopaque, u64, *const UiNode, flex_layout.Size, flex_layout.Rect) UiScroll = null,    text_selection: ?*const fn (?*anyopaque, u64, *const UiNode) UiTextSelection = null,    text_size: ?*const fn (?*anyopaque, *const UiNode) anyerror!?flex_layout.Size = null,};pub const SurfaceFacts = struct {    nodes: usize = 0,    layout_nodes: usize = 0,    children: usize = 0,    pub fn add(self: SurfaceFacts, other: SurfaceFacts) SurfaceFacts {        return .{            .nodes = self.nodes + other.nodes,            .layout_nodes = self.layout_nodes + other.layout_nodes,            .children = self.children + other.children,        };    }};pub fn surveySurface(surface: *const UiSurfaceTree) error{SurfaceTooDeep}!SurfaceFacts {    var facts = SurfaceFacts{};    try surveyNode(&surface.root, &facts, 0);    return facts;}fn surveyNode(node: *const UiNode, facts: *SurfaceFacts, depth: usize) error{SurfaceTooDeep}!void {    if (depth >= max_depth) return error.SurfaceTooDeep;    facts.nodes += 1;    facts.layout_nodes += 1;    facts.children += node.children.len;    for (node.children) |*child| {        try surveyNode(child, facts, depth + 1);    }}/// Validates solved spans and derives exact shell-layout and frame demand.pub fn surveyFrameRoot(root: SurfaceFrameRoot) !SurfaceFacts {    var facts = try surveySurface(root.surface);    try validateSolvedSubtrees(root.surface, root.solved_subtrees);    for (root.solved_subtrees) |subtree| {        facts.nodes = std.math.add(usize, facts.nodes, subtree.bindings.len) catch            return error.SurfaceTooLarge;    }    return facts;}fn validateSolvedSubtrees(surface: *const UiSurfaceTree, subtrees: []const SolvedSubtree) !void {    for (subtrees, 0..) |subtree, subtree_index| {        for (subtrees[0..subtree_index]) |earlier| {            if (earlier.viewport_widget_id == subtree.viewport_widget_id) {                return error.DuplicateSolvedViewport;            }        }        const viewport_depth = try opaqueViewportDepth(&surface.root, subtree.viewport_widget_id, 0);        const subtree_depth = try validateSolvedSubtree(subtree);        if (viewport_depth + 1 + subtree_depth >= max_depth) return error.SurfaceTooDeep;    }}fn opaqueViewportDepth(node: *const UiNode, widget_id: u64, depth: usize) !usize {    var match_depth: ?usize = null;    try findViewport(node, widget_id, depth, &match_depth);    return match_depth orelse error.SolvedViewportNotFound;}fn findViewport(node: *const UiNode, widget_id: u64, depth: usize, match_depth: *?usize) !void {    if (node.widget_id == widget_id) {        if (match_depth.* != null) return error.AmbiguousSolvedViewport;        if (node.children.len != 0) return error.SolvedViewportNotOpaque;        match_depth.* = depth;    }    for (node.children) |*child| try findViewport(child, widget_id, depth + 1, match_depth);}fn validateSolvedSubtree(subtree: SolvedSubtree) !usize {    const count = subtree.bindings.len;    if (count == 0 or        subtree.rectangles.len != count or        subtree.clips.len != count or        subtree.offsets.len != count)    {        return error.InvalidSolvedSpans;    }    if (subtree.bindings[0].subtree_size != count) return error.InvalidSolvedBindings;    var ancestor_ends: [max_depth]usize = undefined;    var ancestor_count: usize = 0;    var maximum_depth: usize = 0;    for (subtree.bindings, 0..) |binding, index| {        while (ancestor_count > 0 and index == ancestor_ends[ancestor_count - 1]) {            ancestor_count -= 1;        }        if (index != 0 and ancestor_count == 0) return error.InvalidSolvedBindings;        const subtree_end = std.math.add(usize, index, binding.subtree_size) catch            return error.InvalidSolvedBindings;        const parent_end = if (ancestor_count == 0) count else ancestor_ends[ancestor_count - 1];        if (binding.subtree_size == 0 or subtree_end > parent_end) return error.InvalidSolvedBindings;        maximum_depth = @max(maximum_depth, ancestor_count);        if (binding.subtree_size > 1) {            if (ancestor_count >= max_depth) return error.SurfaceTooDeep;            ancestor_ends[ancestor_count] = subtree_end;            ancestor_count += 1;        }        if (!validSolvedRect(subtree.rectangles[index]) or            !validSolvedRect(subtree.clips[index]) or            !validSolvedOffset(subtree.offsets[index]))        {            return error.InvalidSolvedGeometry;        }    }    while (ancestor_count > 0 and count == ancestor_ends[ancestor_count - 1]) {        ancestor_count -= 1;    }    if (ancestor_count != 0) return error.InvalidSolvedBindings;    return maximum_depth;}fn validSolvedRect(rect: flex_layout.Rect) bool {    return std.math.isFinite(rect.x) and        std.math.isFinite(rect.y) and        std.math.isFinite(rect.width) and        std.math.isFinite(rect.height) and        rect.width >= 0 and        rect.height >= 0;}fn validSolvedOffset(offset: Offset) bool {    return std.math.isFinite(offset.x) and std.math.isFinite(offset.y);}pub const FrameStorage = struct {    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "gui.frame_storage",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "root_frame_slots_at_the_admitted_root_quota",                        .lifetime = .steady,                        .detail = "root frame slots at the admitted root quota",                    },                    .{                        .id = "widget_frame_slots_at_the_surveyed_shell_and_solved_node_count",                        .lifetime = .steady,                        .detail = "widget frame slots at the surveyed shell and solved node count",                    },                    .{                        .id = "flex_node_spans_at_the_surveyed_child_count",                        .lifetime = .steady,                        .detail = "flex node spans at the surveyed child count",                    },                    .{                        .id = "layout_node_state_spans_at_the_surveyed_shell_node_count",                        .lifetime = .steady,                        .detail = "layout node-state spans at the surveyed shell node count",                    },                    .{                        .id = "layout_child_index_result_metric_and_line_spans",                        .lifetime = .steady,                        .detail = "layout child-index, result, metric, and line spans",                    },                },                .excluded = &.{                    "the caller-owned UiSurfaceTree, solved spans, and strings",                    "resolver callbacks and any storage they touch",                    "sdfii semantics byte ownership and root-id ordering scratch",                    "paint command, atlas, and executor storage",                    "epoch replacement allocation and release outside one admitted demand epoch",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(Limits, "roots", "roots"),                    alloc_phase.capacity.bindInput(Limits, "nodes", "nodes"),                    alloc_phase.capacity.bindInput(Limits, "children", "children"),                    alloc_phase.capacity.bindInput(Limits, "layout_nodes", "layout_nodes"),                },                .type_selectors = &.{                    alloc_phase.capacity.bindType(RootFrame, "root"),                    alloc_phase.capacity.bindType(WidgetFrame, "widget"),                    alloc_phase.capacity.bindType(flex_layout.NodeState, "state"),                    alloc_phase.capacity.bindType(flex_layout.Node, "node"),                    alloc_phase.capacity.bindType(flex_layout.LayoutResult, "result"),                    alloc_phase.capacity.bindType(flex_layout.ChildIndex, "index"),                    alloc_phase.capacity.bindType(flex_layout.ChildMetrics, "metric"),                    alloc_phase.capacity.bindType(flex_layout.Line, "line"),                },                .nodes = &.{                    .{ .input = 0 },                    .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 } } },                    .{ .input = 1 },                    .{ .constant = 1 },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 1 } } },                    .{ .product = .{ .left = 2, .right = 4 } },                    .{ .input = 3 },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 2 } } },                    .{ .product = .{ .left = 6, .right = 7 } },                    .{ .input = 2 },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 3 } } },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 4 } } },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 5 } } },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 6 } } },                    .{ .scale = .{ .node = 3, .coefficient = .{ .size_of_concrete_type = 7 } } },                    .{ .add = .{ .left = 10, .right = 11 } },                    .{ .add = .{ .left = 15, .right = 12 } },                    .{ .add = .{ .left = 16, .right = 13 } },                    .{ .add = .{ .left = 17, .right = 14 } },                    .{ .product = .{ .left = 9, .right = 18 } },                    .{ .add = .{ .left = 1, .right = 5 } },                    .{ .add = .{ .left = 20, .right = 8 } },                    .{ .add = .{ .left = 21, .right = 19 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 22,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "surveyFrameRoot rejects invalid or over-deep shell and solved trees before acquisition; demand past the admitted epoch re-derives storage at the new high water before building; carve past admitted capacity is a capacity-model bug caught by assertion",            },            .risks = .{                .transitive = .{                    .status = .open,                    .detail = "the build crosses layout, widget query, and model helpers without a machine-checked call-graph closure certificate",                },                .foreign = .{                    .status = .open,                    .detail = "resolver callbacks (text measurement, scroll, selection) run host code outside the claim during builds",                },            },            .obligations = &.{                .{ .key = "gui_frame_capacity", .role = .capacity_model },                .{ .key = "gui_frame_acquisition", .role = .custom },                .{ .key = "gui_frame_survey", .role = .overload },                .{ .key = "gui_frame_highwater", .role = .overload },                .{ .key = "gui_frame_steady_transitive_risk", .role = .transitive_risk },                .{ .key = "gui_frame_steady_foreign_risk", .role = .foreign_risk },                .{ .key = "gui_frame_oom", .role = .custom },                .{ .key = "gui_frame_session", .role = .custom },                .{ .key = "gui_frame_session_steady", .role = .transitive_risk },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },        },    };    pub const Limits = struct {        nodes: usize,        layout_nodes: usize,        children: usize,        roots: usize,    };    pub const Capacity = struct {        roots: usize,        widgets: usize,        flex_nodes: usize,        layout_results: usize,        layout_metrics: usize,        layout_lines: usize,        layout_states: usize,        layout_child_indices: usize,        total_bytes: usize,        pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {            if (limits.roots == 0) return error.CapacityOverflow;            if (limits.nodes == 0) return error.CapacityOverflow;            if (limits.layout_nodes == 0 or limits.layout_nodes > limits.nodes) return error.CapacityOverflow;            const root_bytes = std.math.mul(                usize,                limits.roots,                @sizeOf(RootFrame),            ) catch return error.CapacityOverflow;            const widget_bytes = std.math.mul(                usize,                limits.nodes,                @sizeOf(WidgetFrame),            ) catch return error.CapacityOverflow;            const state_bytes = std.math.mul(                usize,                limits.layout_nodes,                @sizeOf(flex_layout.NodeState),            ) catch return error.CapacityOverflow;            const per_child = @sizeOf(flex_layout.Node) +                @sizeOf(flex_layout.LayoutResult) +                @sizeOf(flex_layout.ChildIndex) +                @sizeOf(flex_layout.ChildMetrics) +                @sizeOf(flex_layout.Line);            const child_bytes = std.math.mul(                usize,                limits.children,                per_child,            ) catch return error.CapacityOverflow;            const frame_bytes = std.math.add(                usize,                root_bytes,                widget_bytes,            ) catch return error.CapacityOverflow;            const owned_node_bytes = std.math.add(                usize,                frame_bytes,                state_bytes,            ) catch return error.CapacityOverflow;            const total_bytes = std.math.add(                usize,                owned_node_bytes,                child_bytes,            ) catch return error.CapacityOverflow;            return .{                .roots = limits.roots,                .widgets = limits.nodes,                .flex_nodes = limits.children,                .layout_results = limits.children,                .layout_metrics = limits.children,                .layout_lines = limits.children,                .layout_states = limits.layout_nodes,                .layout_child_indices = limits.children,                .total_bytes = total_bytes,            };        }    };    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    limits: Limits,    roots: []RootFrame,    roots_used: usize,    widgets: []WidgetFrame,    widgets_used: usize,    flex_nodes: []flex_layout.Node,    flex_used: usize,    layout_scratch: flex_layout.Scratch,    pub fn init(allocator: Allocator, limits: Limits) !FrameStorage {        const capacity = try Capacity.derive(limits);        const roots = try allocator.alloc(RootFrame, capacity.roots);        errdefer allocator.free(roots);        const widgets = try allocator.alloc(WidgetFrame, capacity.widgets);        errdefer allocator.free(widgets);        const flex_nodes = try allocator.alloc(flex_layout.Node, capacity.flex_nodes);        errdefer allocator.free(flex_nodes);        const results = try allocator.alloc(flex_layout.LayoutResult, capacity.layout_results);        errdefer allocator.free(results);        const metrics = try allocator.alloc(flex_layout.ChildMetrics, capacity.layout_metrics);        errdefer allocator.free(metrics);        const lines = try allocator.alloc(flex_layout.Line, capacity.layout_lines);        errdefer allocator.free(lines);        const states = try allocator.alloc(flex_layout.NodeState, capacity.layout_states);        errdefer allocator.free(states);        const child_indices = try allocator.alloc(            flex_layout.ChildIndex,            capacity.layout_child_indices,        );        return .{            .phase = .initialization,            .capacity = capacity,            .limits = limits,            .roots = roots,            .roots_used = 0,            .widgets = widgets,            .widgets_used = 0,            .flex_nodes = flex_nodes,            .flex_used = 0,            .layout_scratch = .{                .results = results,                .metrics = metrics,                .lines = lines,                .states = states,                .child_indices = child_indices,            },        };    }    pub fn activate(self: *FrameStorage) void {        std.debug.assert(self.phase == .initialization);        self.assertStorage();        self.phase = .steady;    }    pub fn deinit(self: *FrameStorage, allocator: Allocator) void {        std.debug.assert(self.phase != .teardown);        self.assertStorage();        self.phase = .teardown;        allocator.free(self.roots);        allocator.free(self.widgets);        allocator.free(self.flex_nodes);        allocator.free(self.layout_scratch.results);        allocator.free(self.layout_scratch.metrics);        allocator.free(self.layout_scratch.lines);        allocator.free(self.layout_scratch.states);        allocator.free(self.layout_scratch.child_indices);    }    pub fn admits(self: *const FrameStorage, facts: SurfaceFacts, roots: usize) bool {        std.debug.assert(self.phase == .steady);        if (facts.nodes > self.capacity.widgets) return false;        if (facts.layout_nodes > self.capacity.layout_states) return false;        if (facts.children > self.capacity.flex_nodes) return false;        return roots <= self.capacity.roots;    }    pub fn reset(self: *FrameStorage) void {        std.debug.assert(self.phase == .steady);        self.roots_used = 0;        self.widgets_used = 0;        self.flex_used = 0;        self.layout_scratch.reset();    }    fn carveFlexNodes(self: *FrameStorage, count: usize) []flex_layout.Node {        std.debug.assert(self.flex_used + count <= self.flex_nodes.len);        const span = self.flex_nodes[self.flex_used..][0..count];        self.flex_used += count;        return span;    }    fn assertStorage(self: *const FrameStorage) void {        const expected = Capacity.derive(self.limits) catch unreachable;        std.debug.assert(std.meta.eql(expected, self.capacity));        std.debug.assert(self.roots.len == self.capacity.roots);        std.debug.assert(self.widgets.len == self.capacity.widgets);        std.debug.assert(self.flex_nodes.len == self.capacity.flex_nodes);        std.debug.assert(self.layout_scratch.results.len == self.capacity.layout_results);        std.debug.assert(self.layout_scratch.metrics.len == self.capacity.layout_metrics);        std.debug.assert(self.layout_scratch.lines.len == self.capacity.layout_lines);        std.debug.assert(self.layout_scratch.states.len == self.capacity.layout_states);        std.debug.assert(            self.layout_scratch.child_indices.len == self.capacity.layout_child_indices,        );    }};comptime {    alloc_phase.capacity.requireAllocatorExactOwnerShape(FrameStorage);}fn modelFrameCapacity(limits: FrameStorage.Limits) error{CapacityOverflow}!FrameStorage.Capacity {    if (limits.roots == 0) return error.CapacityOverflow;    if (limits.nodes == 0) return error.CapacityOverflow;    if (limits.layout_nodes == 0 or limits.layout_nodes > limits.nodes) return error.CapacityOverflow;    const per_child: u128 = @sizeOf(flex_layout.Node) +        @sizeOf(flex_layout.LayoutResult) +        @sizeOf(flex_layout.ChildIndex) +        @sizeOf(flex_layout.ChildMetrics) +        @sizeOf(flex_layout.Line);    const total = @as(u128, limits.roots) * @sizeOf(RootFrame) +        @as(u128, limits.nodes) * @sizeOf(WidgetFrame) +        @as(u128, limits.layout_nodes) * @sizeOf(flex_layout.NodeState) +        @as(u128, limits.children) * per_child;    if (total > std.math.maxInt(usize)) return error.CapacityOverflow;    return .{        .roots = limits.roots,        .widgets = limits.nodes,        .flex_nodes = limits.children,        .layout_results = limits.children,        .layout_metrics = limits.children,        .layout_lines = limits.children,        .layout_states = limits.layout_nodes,        .layout_child_indices = limits.children,        .total_bytes = @intCast(total),    };}comptime {    const worst = FrameStorage.Limits{        .nodes = std.math.maxInt(u32),        .layout_nodes = std.math.maxInt(u32),        .children = std.math.maxInt(u32),        .roots = std.math.maxInt(u16),    };    _ = modelFrameCapacity(worst) catch @compileError("frame capacity exceeds the target word size");}pub const Workspace = struct {    allocator: Allocator,    storage: ?FrameStorage = null,    pub fn init(allocator: Allocator) Workspace {        return .{ .allocator = allocator };    }    pub fn deinit(self: *Workspace) void {        if (self.storage) |*storage| storage.deinit(self.allocator);        self.* = undefined;    }    pub fn buildSurface(self: *Workspace, surface: *const UiSurfaceTree, options: SurfaceFrameOptions) !UiFrame {        const root = SurfaceFrameRoot{            .surface = surface,            .root_id = options.root_id,            .revision = options.revision,            .hovered_widget = options.hovered_widget,            .hovered_point = options.hovered_point,            .captured_widget = options.captured_widget,            .focused_widget = options.focused_widget,            .solved_subtrees = options.solved_subtrees,        };        const facts = try surveyFrameRoot(root);        const storage = try self.ensureStorage(facts, 1);        storage.reset();        appendSurfaceFrame(            storage,            root,            options.resolvers,        ) catch |err| {            storage.reset();            return err;        };        return .{            .revision = options.revision,            .root_count = storage.roots_used,            .roots = storage.roots[0..storage.roots_used],            .widget_count = storage.widgets_used,            .widgets = storage.widgets[0..storage.widgets_used],        };    }    fn ensureStorage(self: *Workspace, facts: SurfaceFacts, roots: usize) !*FrameStorage {        if (self.storage) |*storage| {            if (storage.admits(facts, roots)) return storage;        }        const grown = FrameStorage.Limits{            .nodes = @max(facts.nodes, if (self.storage) |s| s.capacity.widgets else 0),            .layout_nodes = @max(facts.layout_nodes, if (self.storage) |s| s.capacity.layout_states else 0),            .children = @max(facts.children, if (self.storage) |s| s.capacity.flex_nodes else 0),            .roots = @max(roots, if (self.storage) |s| s.capacity.roots else 0),        };        var next = try FrameStorage.init(self.allocator, grown);        next.activate();        if (self.storage) |*storage| storage.deinit(self.allocator);        self.storage = next;        return &self.storage.?;    }};pub const Session = struct {    allocator: Allocator,    front: ?FrameStorage = null,    back: ?FrameStorage = null,    pub fn init(allocator: Allocator) Session {        return .{ .allocator = allocator };    }    pub fn deinit(self: *Session) void {        if (self.front) |*storage| storage.deinit(self.allocator);        if (self.back) |*storage| storage.deinit(self.allocator);        self.* = undefined;    }    pub fn begin(self: *Session, facts: SurfaceFacts, roots: usize) !*FrameStorage {        const admitted_roots = @max(roots, 1);        if (self.back) |*storage| {            if (storage.admits(facts, admitted_roots)) {                storage.reset();                return storage;            }        }        const grown = FrameStorage.Limits{            .nodes = @max(facts.nodes, if (self.back) |s| s.capacity.widgets else 0),            .layout_nodes = @max(facts.layout_nodes, if (self.back) |s| s.capacity.layout_states else 0),            .children = @max(facts.children, if (self.back) |s| s.capacity.flex_nodes else 0),            .roots = @max(admitted_roots, if (self.back) |s| s.capacity.roots else 0),        };        var next = try FrameStorage.init(self.allocator, grown);        next.activate();        if (self.back) |*storage| storage.deinit(self.allocator);        self.back = next;        return &self.back.?;    }    pub fn append(self: *Session, root: SurfaceFrameRoot, resolvers: FrameResolvers) !void {        std.debug.assert(self.back != null);        try appendSurfaceFrame(&self.back.?, root, resolvers);    }    pub fn stagedWidgets(self: *Session) []WidgetFrame {        std.debug.assert(self.back != null);        const storage = &self.back.?;        return storage.widgets[0..storage.widgets_used];    }    pub fn publish(self: *Session, revision: u64) UiFrame {        std.debug.assert(self.back != null);        std.mem.swap(?FrameStorage, &self.front, &self.back);        const storage = &self.front.?;        return .{            .revision = revision,            .root_count = storage.roots_used,            .roots = storage.roots[0..storage.roots_used],            .widget_count = storage.widgets_used,            .widgets = storage.widgets[0..storage.widgets_used],        };    }};pub fn appendSurfaceFrame(    storage: *FrameStorage,    root: SurfaceFrameRoot,    resolvers: FrameResolvers,) !void {    std.debug.assert(storage.phase == .steady);    const facts = try surveyFrameRoot(root);    if (storage.roots_used >= storage.roots.len or        facts.nodes > storage.widgets.len - storage.widgets_used or        facts.children > storage.flex_nodes.len - storage.flex_used or        facts.layout_nodes > storage.layout_scratch.states.len or        facts.children > storage.layout_scratch.results.len or        facts.children > storage.layout_scratch.metrics.len or        facts.children > storage.layout_scratch.lines.len or        facts.children > storage.layout_scratch.child_indices.len)    {        return error.FrameStorageCapacity;    }    const widget_start = storage.widgets_used;    const flex_start = storage.flex_used;    const root_start = storage.roots_used;    const scratch_marks = storage.layout_scratch;    errdefer {        storage.widgets_used = widget_start;        storage.flex_used = flex_start;        storage.roots_used = root_start;        storage.layout_scratch.results_used = scratch_marks.results_used;        storage.layout_scratch.metrics_used = scratch_marks.metrics_used;        storage.layout_scratch.lines_used = scratch_marks.lines_used;    }    const flex_root = try buildFlexNodeWithResolvers(storage, &root.surface.root, resolvers, 0);    const layout = flex_layout.computeLayout(&storage.layout_scratch, flex_root, root.surface.available_size);    std.debug.assert(storage.roots_used < storage.roots.len);    const root_index = storage.roots_used;    storage.roots[root_index] = .{        .root_id = root.root_id,        .surface_revision = root.revision,        .rect = snapRect(layout.rect),        .widget_start = widget_start,        .widget_count = 0,    };    storage.roots_used += 1;    appendWidgetFrames(        storage,        root,        resolvers,        &root.surface.root,        &layout,        .{},        null,        0,        0,    );    if (root.hovered_widget == null) markHoveredPoint(storage.widgets[widget_start..storage.widgets_used], root.root_id, root.hovered_point);    storage.roots[root_index].widget_count = storage.widgets_used - widget_start;}fn markHoveredPoint(widgets: []WidgetFrame, root_id: u64, hovered_point: ?UiPoint) void {    const point = hovered_point orelse return;    const hovered = widget_query.hitTestActivatable(widgets, root_id, point.x, point.y) orelse        widget_query.hitTest(widgets, root_id, point.x, point.y) orelse        return;    const index = widget_query.findIndexById(widgets, root_id, hovered.widget_id) orelse return;    widgets[index].hovered = true;}fn buildFlexNodeWithResolvers(    storage: *FrameStorage,    node: *const UiNode,    resolvers: FrameResolvers,    depth: usize,) !flex_layout.Node {    std.debug.assert(depth < max_depth);    const children = storage.carveFlexNodes(node.children.len);    for (node.children, 0..) |*child, index| {        children[index] = try buildFlexNodeWithResolvers(storage, child, resolvers, depth + 1);    }    const intrinsic_size = if (resolvers.text_size) |resolver| blk: {        const measured = try resolver(resolvers.context, node);        break :blk measured orelse flex_layout.Size{};    } else flex_layout.Size{};    return .{        .id = std.math.cast(usize, node.widget_id) orelse 0,        .style = node.style,        .size = node.size,        .intrinsic_size = intrinsic_size,        .clip_x = node.scroll.overflow_x != .visible,        .clip_y = node.scroll.overflow_y != .visible,        .children = children,    };}fn appendWidgetFrames(    storage: *FrameStorage,    root: SurfaceFrameRoot,    resolvers: FrameResolvers,    node: *const UiNode,    layout: *const flex_layout.LayoutResult,    offset: Offset,    active_clip: ?flex_layout.Rect,    parent_layer: u8,    depth: usize,) void {    std.debug.assert(depth < max_depth);    const layer = @max(parent_layer, node.layer);    const frame_rect = snapRect(offsetRect(layout.rect, offset.x, offset.y));    const visible_rect = if (active_clip) |clip| intersectRects(frame_rect, clip) else frame_rect;    const content_size_value = contentSize(layout);    const scroll = resolveScroll(resolvers, root.root_id, node, content_size_value, frame_rect);    const text_selection = resolveTextSelection(resolvers, root.root_id, node);    const frame_text = node.text;    const has_text = if (frame_text) |text| text.content.len != 0 else false;    const text_ref = if (has_text) makeTextRef(root.root_id, node.widget_id) else 0;    const widget_index = storage.widgets_used;    std.debug.assert(widget_index < storage.widgets.len);    storage.widgets[widget_index] = .{        .root_id = root.root_id,        .widget_id = node.widget_id,        .kind = node.kind,        .rect = frame_rect,        .visible_rect = visible_rect,        .paint = node.paint,        .scroll = scroll,        .constraints = node.style.constraints,        .content_size = content_size_value,        .focusable = (node.focusable or node.kind == .button or node.kind == .text_input) and !widget_query.isDisabledNode(node),        .layer = layer,        .hovered = root.hovered_widget == node.widget_id,        .captured = root.captured_widget == node.widget_id,        .focused = root.focused_widget == node.widget_id,        .has_text = has_text,        .action = node.action,        .role = node.role,        .state_flags = node.state_flags,        .text_ref = text_ref,        .text = frame_text,        .text_selection = text_selection,    };    storage.widgets_used += 1;    const child_offset = Offset{        .x = offset.x - scroll.scroll_x,        .y = offset.y - scroll.scroll_y,    };    const next_clip = childClip(active_clip, frame_rect, scroll);    if (solvedSubtreeForViewport(root.solved_subtrees, node.widget_id)) |subtree| {        _ = appendSolvedNode(            storage,            root,            resolvers,            subtree,            0,            frame_rect,            visible_rect,            layer,        );    }    for (node.children, 0..) |*child_node, index| {        appendWidgetFrames(            storage,            root,            resolvers,            child_node,            &layout.children[index],            child_offset,            next_clip,            layer,            depth + 1,        );    }    storage.widgets[widget_index].subtree_size = storage.widgets_used - widget_index;}fn solvedSubtreeForViewport(subtrees: []const SolvedSubtree, widget_id: u64) ?*const SolvedSubtree {    for (subtrees) |*subtree| {        if (subtree.viewport_widget_id == widget_id) return subtree;    }    return null;}fn appendSolvedNode(    storage: *FrameStorage,    root: SurfaceFrameRoot,    resolvers: FrameResolvers,    subtree: *const SolvedSubtree,    binding_index: usize,    viewport_rect: flex_layout.Rect,    viewport_clip: flex_layout.Rect,    parent_layer: u8,) usize {    const binding = subtree.bindings[binding_index];    const node = binding.node;    const layer = @max(parent_layer, node.layer);    const local_rect = subtree.rectangles[binding_index];    const node_offset = subtree.offsets[binding_index];    const frame_rect = snapRect(offsetRect(        local_rect,        viewport_rect.x + node_offset.x,        viewport_rect.y + node_offset.y,    ));    const solved_clip = offsetRect(        subtree.clips[binding_index],        viewport_rect.x,        viewport_rect.y,    );    const visible_rect = intersectRects(frame_rect, intersectRects(viewport_clip, solved_clip));    const content_size_value = solvedContentSize(subtree, binding_index);    const scroll = resolveScroll(resolvers, root.root_id, node, content_size_value, frame_rect);    const text_selection = resolveTextSelection(resolvers, root.root_id, node);    const frame_text = node.text;    const has_text = if (frame_text) |value| value.content.len != 0 else false;    const text_ref = if (has_text) makeTextRef(root.root_id, node.widget_id) else 0;    const widget_index = storage.widgets_used;    std.debug.assert(widget_index < storage.widgets.len);    storage.widgets[widget_index] = .{        .root_id = root.root_id,        .widget_id = node.widget_id,        .kind = node.kind,        .rect = frame_rect,        .visible_rect = visible_rect,        .paint = node.paint,        .scroll = scroll,        .constraints = node.style.constraints,        .content_size = content_size_value,        .focusable = (node.focusable or node.kind == .button or node.kind == .text_input) and !widget_query.isDisabledNode(node),        .layer = layer,        .hovered = root.hovered_widget == node.widget_id,        .captured = root.captured_widget == node.widget_id,        .focused = root.focused_widget == node.widget_id,        .has_text = has_text,        .action = node.action,        .role = node.role,        .state_flags = node.state_flags,        .text_ref = text_ref,        .text = frame_text,        .text_selection = text_selection,        .subtree_size = binding.subtree_size,    };    storage.widgets_used += 1;    var child_index = binding_index + 1;    const subtree_end = binding_index + binding.subtree_size;    while (child_index < subtree_end) {        child_index = appendSolvedNode(            storage,            root,            resolvers,            subtree,            child_index,            viewport_rect,            viewport_clip,            layer,        );    }    return subtree_end;}fn solvedContentSize(subtree: *const SolvedSubtree, binding_index: usize) flex_layout.Size {    const rect = subtree.rectangles[binding_index];    var right = rect.x + rect.width;    var bottom = rect.y + rect.height;    const subtree_end = binding_index + subtree.bindings[binding_index].subtree_size;    for (subtree.rectangles[binding_index + 1 .. subtree_end]) |descendant| {        right = @max(right, descendant.x + descendant.width);        bottom = @max(bottom, descendant.y + descendant.height);    }    return .{        .width = @max(right - rect.x, rect.width),        .height = @max(bottom - rect.y, rect.height),    };}fn resolveScroll(    resolvers: FrameResolvers,    root_id: u64,    node: *const UiNode,    node_content_size: flex_layout.Size,    rect: flex_layout.Rect,) UiScroll {    if (resolvers.scroll) |resolver| return resolver(resolvers.context, root_id, node, node_content_size, rect);    return clampScroll(node.scroll, node_content_size, rect);}fn resolveTextSelection(    resolvers: FrameResolvers,    root_id: u64,    node: *const UiNode,) UiTextSelection {    const selection = if (resolvers.text_selection) |resolver|        resolver(resolvers.context, root_id, node)    else        node.text_selection;    const text_len = if (node.text) |text| text.content.len else 0;    return normalizeTextSelection(selection, text_len);}pub const pointInRect = model.pointInRect;pub fn offsetRect(rect: flex_layout.Rect, x: f32, y: f32) flex_layout.Rect {    return .{        .x = rect.x + x,        .y = rect.y + y,        .width = rect.width,        .height = rect.height,    };}pub fn snapRect(rect: flex_layout.Rect) flex_layout.Rect {    const left = @round(rect.x);    const top = @round(rect.y);    const right = @round(rect.x + rect.width);    const bottom = @round(rect.y + rect.height);    return .{        .x = left,        .y = top,        .width = right - left,        .height = bottom - top,    };}pub fn contentSize(layout: *const flex_layout.LayoutResult) flex_layout.Size {    var right = layout.rect.x + layout.rect.width;    var bottom = layout.rect.y + layout.rect.height;    for (layout.children) |child| {        right = @max(right, child.rect.x + child.rect.width);        bottom = @max(bottom, child.rect.y + child.rect.height);    }    return .{        .width = @max(right - layout.rect.x, layout.rect.width),        .height = @max(bottom - layout.rect.y, layout.rect.height),    };}pub const Offset = struct {    x: f32 = 0,    y: f32 = 0,};pub fn childClip(current: ?flex_layout.Rect, rect: flex_layout.Rect, scroll: UiScroll) ?flex_layout.Rect {    if (scroll.overflow_x == .visible and scroll.overflow_y == .visible) return current;    const next = flex_layout.Rect{        .x = if (scroll.overflow_x == .clip) rect.x else -clip_extent,        .y = if (scroll.overflow_y == .clip) rect.y else -clip_extent,        .width = if (scroll.overflow_x == .clip) rect.width else clip_extent * 2,        .height = if (scroll.overflow_y == .clip) rect.height else clip_extent * 2,    };    return if (current) |clip| intersectRects(clip, next) else next;}pub fn intersectRects(a: flex_layout.Rect, b: flex_layout.Rect) flex_layout.Rect {    const left = @max(a.x, b.x);    const top = @max(a.y, b.y);    const right = @min(a.x + a.width, b.x + b.width);    const bottom = @min(a.y + a.height, b.y + b.height);    return .{        .x = left,        .y = top,        .width = @max(right - left, 0),        .height = @max(bottom - top, 0),    };}pub fn clampScroll(scroll: UiScroll, content_size: flex_layout.Size, rect: flex_layout.Rect) UiScroll {    var result = scroll;    result.scroll_x = nonNegativeFinite(result.scroll_x);    result.scroll_y = nonNegativeFinite(result.scroll_y);    if (result.overflow_x == .clip) result.scroll_x = std.math.clamp(result.scroll_x, 0, maxScrollX(content_size, rect));    if (result.overflow_y == .clip) result.scroll_y = std.math.clamp(result.scroll_y, 0, maxScrollY(content_size, rect));    return result;}pub fn scrollForVisibleTarget(widget: *const WidgetFrame, current: UiScroll, target_rect: flex_layout.Rect) ?UiScroll {    var next = current;    const viewport = widget.visible_rect;    if (current.overflow_x == .clip and viewport.width > 0) {        next.scroll_x = scrollOffsetForVisibleRange(current.scroll_x, viewport.x, viewport.width, target_rect.x, target_rect.width);    }    if (current.overflow_y == .clip and viewport.height > 0) {        next.scroll_y = scrollOffsetForVisibleRange(current.scroll_y, viewport.y, viewport.height, target_rect.y, target_rect.height);    }    next = clampScroll(next, widget.content_size, widget.rect);    if (next.scroll_x == current.scroll_x and next.scroll_y == current.scroll_y) return null;    return next;}fn scrollOffsetForVisibleRange(current_scroll: f32, viewport_start: f32, viewport_size: f32, target_start: f32, target_size: f32) f32 {    const viewport_end = viewport_start + viewport_size;    const target_end = target_start + target_size;    if (target_start <= viewport_start and viewport_end <= target_end) return current_scroll;    if (viewport_start <= target_start and target_end <= viewport_end) return current_scroll;    if (target_start < viewport_start) return current_scroll - (viewport_start - target_start);    if (target_end > viewport_end) return current_scroll + (target_end - viewport_end);    return current_scroll;}pub fn maxScrollX(content_size: flex_layout.Size, rect: flex_layout.Rect) f32 {    return @max(content_size.width - rect.width, 0);}pub fn maxScrollY(content_size: flex_layout.Size, rect: flex_layout.Rect) f32 {    return @max(content_size.height - rect.height, 0);}pub fn makeTextRef(root_id: u64, widget_id: u64) u64 {    var pair = [2]u64{ root_id, widget_id };    const hashed = std.hash.Wyhash.hash(0, std.mem.asBytes(&pair)) & std.math.maxInt(i64);    return if (hashed == 0) 1 else hashed;}pub fn normalizeTextSelection(selection: UiTextSelection, text_len: usize) UiTextSelection {    var result = selection;    result.cursor_byte_offset = @min(result.cursor_byte_offset, text_len);    result.selection_anchor_byte_offset = @min(result.selection_anchor_byte_offset, text_len);    result.selection_focus_byte_offset = @min(result.selection_focus_byte_offset, text_len);    if (result.selection_anchor_byte_offset == result.selection_focus_byte_offset) result.selection_active = false;    return result;}const clip_extent: f32 = 1_000_000;test "layers propagate from a subtree root to its descendants" {    const allocator = std.testing.allocator;    const overlay_children = [_]UiNode{        .{            .widget_id = 4,            .size = .{ .width = 40, .height = 10 },            .text = .{ .content = "modal" },        },    };    const children = [_]UiNode{        .{            .widget_id = 2,            .size = .{ .width = 80, .height = 24 },            .text = .{ .content = "base" },        },        .{            .widget_id = 3,            .layer = 1,            .style = .{ .position = .absolute, .inset = .{ .left = 10, .top = 5 } },            .size = .{ .width = 60, .height = 20 },            .children = overlay_children[0..],        },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 200, .height = 40 },        .root = .{            .widget_id = 1,            .children = children[0..],        },    };    var workspace = Workspace.init(allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{ .root_id = 42, .revision = 1 });    const base = widget_query.findById(frame.widgets, 42, 2) orelse return error.MissingBaseWidget;    try std.testing.expectEqual(@as(u8, 0), base.layer);    const panel = widget_query.findById(frame.widgets, 42, 3) orelse return error.MissingPanelWidget;    try std.testing.expectEqual(@as(u8, 1), panel.layer);    const descendant = widget_query.findById(frame.widgets, 42, 4) orelse return error.MissingDescendantWidget;    try std.testing.expectEqual(@as(u8, 1), descendant.layer);}test "surface frame facts preserve roles actions and hit geometry" {    const allocator = std.testing.allocator;    const children = [_]UiNode{        .{            .widget_id = 2,            .kind = .button,            .size = .{ .width = 80, .height = 24 },            .text = .{ .content = "Run" },            .action = "demo.run",            .role = "demo.command",        },        .{            .widget_id = 3,            .kind = .button,            .size = .{ .width = 80, .height = 24 },            .text = .{ .content = "Stop" },            .action = "demo.stop",            .role = "demo.command",            .state_flags = model.ui_state_disabled,        },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 200, .height = 40 },        .root = .{            .widget_id = 1,            .style = .{                .flex_direction = .row,                .gap = 8,            },            .children = children[0..],        },    };    var workspace = Workspace.init(allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{        .root_id = 42,        .revision = 7,        .hovered_widget = 2,        .focused_widget = 2,    });    try std.testing.expectEqual(@as(u64, 7), frame.revision);    try std.testing.expectEqual(@as(usize, 1), frame.root_count);    try std.testing.expectEqual(@as(usize, 3), frame.widget_count);    const run = widget_query.findById(frame.widgets, 42, 2) orelse return error.MissingRunWidget;    try std.testing.expect(run.focusable);    try std.testing.expect(run.hovered);    try std.testing.expect(run.focused);    try std.testing.expect(run.has_text);    try std.testing.expect(run.text_ref != 0);    try std.testing.expectEqualStrings("demo.run", run.action);    const hit = widget_query.hitTest(frame.widgets, 42, run.rect.x + 1, run.rect.y + 1) orelse return error.MissingHit;    try std.testing.expectEqual(@as(u64, 2), hit.widget_id);    const stop = widget_query.findById(frame.widgets, 42, 3) orelse return error.MissingStopWidget;    try std.testing.expect(!stop.focusable);    const disabled_hit = widget_query.hitTest(frame.widgets, 42, stop.rect.x + 1, stop.rect.y + 1);    if (disabled_hit) |hit_widget| try std.testing.expect(hit_widget.widget_id != 3);}test "surface frame appends an externally solved subtree beneath an opaque viewport" {    const viewport_children = [_]UiNode{.{        .widget_id = 2,        .style = .{            .position = .absolute,            .inset = .{ .left = 20, .top = 10 },        },        .size = .{ .width = 100, .height = 60 },    }};    const surface = UiSurfaceTree{        .available_size = .{ .width = 160, .height = 100 },        .root = .{ .widget_id = 1, .children = &viewport_children },    };    const external_nodes = [_]UiNode{        .{ .widget_id = 10, .role = "document", .layer = 1 },        .{ .widget_id = 11, .kind = .button, .action = "open", .role = "command" },        .{ .widget_id = 12, .kind = .label, .text = .{ .content = "status" }, .layer = 2 },    };    const bindings = [_]SolvedBinding{        .{ .node = &external_nodes[0], .subtree_size = 3 },        .{ .node = &external_nodes[1], .subtree_size = 1 },        .{ .node = &external_nodes[2], .subtree_size = 1 },    };    const rectangles = [_]flex_layout.Rect{        .{ .width = 100, .height = 60 },        .{ .x = 5, .y = 8, .width = 30, .height = 10 },        .{ .x = 5, .y = 30, .width = 80, .height = 20 },    };    const clips = [_]flex_layout.Rect{        .{ .width = 100, .height = 60 },        .{ .width = 20, .height = 15 },        .{ .width = 100, .height = 50 },    };    const offsets = [_]Offset{        .{},        .{ .x = 2, .y = 3 },        .{ .y = -4 },    };    const solved = SolvedSubtree{        .viewport_widget_id = 2,        .bindings = &bindings,        .rectangles = &rectangles,        .clips = &clips,        .offsets = &offsets,    };    const root = SurfaceFrameRoot{        .surface = &surface,        .root_id = 7,        .revision = 9,        .solved_subtrees = &.{solved},    };    const facts = try surveyFrameRoot(root);    try std.testing.expectEqual(@as(usize, 5), facts.nodes);    try std.testing.expectEqual(@as(usize, 2), facts.layout_nodes);    try std.testing.expectEqual(@as(usize, 1), facts.children);    var workspace = Workspace.init(std.testing.allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{        .root_id = root.root_id,        .revision = root.revision,        .solved_subtrees = root.solved_subtrees,    });    try std.testing.expectEqual(@as(usize, 5), frame.widget_count);    try std.testing.expectEqual(@as(usize, 5), frame.widgets[0].subtree_size);    try std.testing.expectEqual(@as(usize, 4), frame.widgets[1].subtree_size);    try std.testing.expectEqual(@as(u64, 10), frame.widgets[2].widget_id);    try std.testing.expectEqual(@as(usize, 3), frame.widgets[2].subtree_size);    const command = widget_query.findById(frame.widgets, 7, 11) orelse        return error.MissingSolvedCommand;    try std.testing.expectEqualStrings("open", command.action);    try std.testing.expectEqual(@as(u8, 1), command.layer);    try std.testing.expectEqual(@as(f32, 27), command.rect.x);    try std.testing.expectEqual(@as(f32, 21), command.rect.y);    try std.testing.expectEqual(@as(f32, 13), command.visible_rect.width);    try std.testing.expectEqual(@as(f32, 4), command.visible_rect.height);    const status = widget_query.findById(frame.widgets, 7, 12) orelse        return error.MissingSolvedStatus;    try std.testing.expectEqual(@as(u8, 2), status.layer);    try std.testing.expectEqual(@as(f32, 36), status.rect.y);    try std.testing.expect(status.has_text);    try std.testing.expectEqual(@as(usize, 2), workspace.storage.?.layout_scratch.states.len);}test "surface frame rejects invalid solved spans before replacing the accepted frame" {    const viewport_children = [_]UiNode{.{        .widget_id = 2,        .size = .{ .width = 16, .height = 16 },    }};    const surface = UiSurfaceTree{        .available_size = .{ .width = 32, .height = 32 },        .root = .{ .widget_id = 1, .children = &viewport_children },    };    const external = UiNode{ .widget_id = 10 };    const bindings = [_]SolvedBinding{.{ .node = &external, .subtree_size = 1 }};    const rectangles = [_]flex_layout.Rect{.{ .width = 8, .height = 8 }};    const clips = [_]flex_layout.Rect{.{ .width = 8, .height = 8 }};    const offsets = [_]Offset{.{}};    const invalid = SolvedSubtree{        .viewport_widget_id = 2,        .bindings = &bindings,        .rectangles = &rectangles,        .clips = &clips,        .offsets = &.{},    };    const valid = SolvedSubtree{        .viewport_widget_id = 2,        .bindings = &bindings,        .rectangles = &rectangles,        .clips = &clips,        .offsets = &offsets,    };    const invalid_bindings = [_]SolvedBinding{.{ .node = &external, .subtree_size = 2 }};    const invalid_geometry = [_]flex_layout.Rect{.{        .x = std.math.nan(f32),        .width = 8,        .height = 8,    }};    try std.testing.expectError(        error.InvalidSolvedBindings,        surveyFrameRoot(.{            .surface = &surface,            .solved_subtrees = &.{.{                .viewport_widget_id = 2,                .bindings = &invalid_bindings,                .rectangles = &rectangles,                .clips = &clips,                .offsets = &offsets,            }},        }),    );    try std.testing.expectError(        error.InvalidSolvedGeometry,        surveyFrameRoot(.{            .surface = &surface,            .solved_subtrees = &.{.{                .viewport_widget_id = 2,                .bindings = &bindings,                .rectangles = &invalid_geometry,                .clips = &clips,                .offsets = &offsets,            }},        }),    );    try std.testing.expectError(        error.SolvedViewportNotFound,        surveyFrameRoot(.{            .surface = &surface,            .solved_subtrees = &.{.{                .viewport_widget_id = 3,                .bindings = &bindings,                .rectangles = &rectangles,                .clips = &clips,                .offsets = &offsets,            }},        }),    );    try std.testing.expectError(        error.DuplicateSolvedViewport,        surveyFrameRoot(.{            .surface = &surface,            .solved_subtrees = &.{ valid, valid },        }),    );    var workspace = Workspace.init(std.testing.allocator);    defer workspace.deinit();    const accepted = try workspace.buildSurface(&surface, .{ .revision = 1 });    const accepted_widgets = accepted.widgets.ptr;    try std.testing.expectError(        error.InvalidSolvedSpans,        workspace.buildSurface(&surface, .{            .revision = 2,            .solved_subtrees = &.{invalid},        }),    );    try std.testing.expectEqual(accepted_widgets, accepted.widgets.ptr);    try std.testing.expectEqual(@as(u64, 1), accepted.revision);    try std.testing.expectEqual(@as(u64, 2), accepted.widgets[1].widget_id);}test "surface frame reuses admitted solved-subtree storage" {    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const viewport_children = [_]UiNode{.{        .widget_id = 2,        .size = .{ .width = 16, .height = 16 },    }};    const surface = UiSurfaceTree{        .available_size = .{ .width = 32, .height = 32 },        .root = .{ .widget_id = 1, .children = &viewport_children },    };    const external_nodes = [_]UiNode{        .{ .widget_id = 10 },        .{ .widget_id = 11, .kind = .label, .text = .{ .content = "ready" } },    };    const bindings = [_]SolvedBinding{        .{ .node = &external_nodes[0], .subtree_size = 2 },        .{ .node = &external_nodes[1], .subtree_size = 1 },    };    const rectangles = [_]flex_layout.Rect{        .{ .width = 16, .height = 16 },        .{ .width = 12, .height = 8 },    };    const clips = [_]flex_layout.Rect{        .{ .width = 16, .height = 16 },        .{ .width = 16, .height = 16 },    };    const offsets = [_]Offset{ .{}, .{} };    const solved = SolvedSubtree{        .viewport_widget_id = 2,        .bindings = &bindings,        .rectangles = &rectangles,        .clips = &clips,        .offsets = &offsets,    };    var workspace = Workspace.init(failing.allocator());    defer workspace.deinit();    for (0..2) |_| {        const frame = try workspace.buildSurface(&surface, .{ .solved_subtrees = &.{solved} });        try std.testing.expectEqual(@as(usize, 4), frame.widget_count);    }    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    for (0..8) |_| {        const frame = try workspace.buildSurface(&surface, .{ .solved_subtrees = &.{solved} });        try std.testing.expectEqual(@as(usize, 4), frame.widget_count);    }    try std.testing.expect(!failing.has_induced_failure);}test "surface frame applies text intrinsic measurement to flex layout" {    const allocator = std.testing.allocator;    const children = [_]UiNode{        .{            .widget_id = 2,            .kind = .label,            .text = .{ .content = "abc" },            .size = .{ .height = 12 },        },        .{            .widget_id = 3,            .kind = .label,            .text = .{ .content = "z" },            .size = .{ .height = 12 },        },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 100, .height = 20 },        .root = .{            .widget_id = 1,            .style = .{ .flex_direction = .row, .gap = 5, .align_items = .flex_start },            .children = children[0..],        },    };    var workspace = Workspace.init(allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{        .resolvers = .{ .text_size = measureTextForFrameTest },    });    const abc = widget_query.findById(frame.widgets, 1, 2) orelse return error.MissingAbc;    const z = widget_query.findById(frame.widgets, 1, 3) orelse return error.MissingZ;    try std.testing.expectEqual(@as(f32, 21), abc.rect.width);    try std.testing.expectEqual(@as(f32, 26), z.rect.x);    try std.testing.expectEqual(@as(f32, 7), z.rect.width);}fn measureTextForFrameTest(_: ?*anyopaque, node: *const UiNode) anyerror!?flex_layout.Size {    const text = node.text orelse return null;    return .{        .width = @as(f32, @floatFromInt(text.content.len)) * 7,        .height = 11,    };}test "frame rects snap to whole pixels and adjacent children share edges" {    const allocator = std.testing.allocator;    const children = [_]UiNode{        .{            .widget_id = 2,            .size = .{ .width = 10.5, .height = 15 },        },        .{            .widget_id = 3,            .size = .{ .width = 10.5, .height = 15 },        },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 40, .height = 20 },        .root = .{            .widget_id = 1,            .style = .{ .flex_direction = .row, .align_items = .center },            .children = children[0..],        },    };    var workspace = Workspace.init(allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{});    const first = widget_query.findById(frame.widgets, 1, 2) orelse return error.MissingFirst;    const second = widget_query.findById(frame.widgets, 1, 3) orelse return error.MissingSecond;    for ([_]flex_layout.Rect{ first.rect, second.rect }) |rect| {        try std.testing.expectEqual(rect.x, @round(rect.x));        try std.testing.expectEqual(rect.y, @round(rect.y));        try std.testing.expectEqual(rect.width, @round(rect.width));        try std.testing.expectEqual(rect.height, @round(rect.height));    }    try std.testing.expectEqual(@as(f32, 3), first.rect.y);    try std.testing.expectEqual(@as(f32, 15), first.rect.height);    try std.testing.expectEqual(@as(f32, 11), first.rect.width);    try std.testing.expectEqual(first.rect.x + first.rect.width, second.rect.x);    try std.testing.expectEqual(@as(f32, 10), second.rect.width);}test "surface frame facts derive hovered widget from point" {    const allocator = std.testing.allocator;    const children = [_]UiNode{        .{            .widget_id = 2,            .kind = .button,            .size = .{ .width = 80, .height = 24 },            .text = .{ .content = "Run" },            .action = "demo.run",        },        .{            .widget_id = 3,            .kind = .button,            .size = .{ .width = 80, .height = 24 },            .text = .{ .content = "Stop" },            .action = "demo.stop",        },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 200, .height = 40 },        .root = .{            .widget_id = 1,            .style = .{                .flex_direction = .row,                .gap = 8,            },            .children = children[0..],        },    };    var workspace = Workspace.init(allocator);    defer workspace.deinit();    const frame = try workspace.buildSurface(&surface, .{        .root_id = 43,        .hovered_point = .{ .x = 92, .y = 12 },    });    const run = widget_query.findById(frame.widgets, 43, 2) orelse return error.MissingRunWidget;    const stop = widget_query.findById(frame.widgets, 43, 3) orelse return error.MissingStopWidget;    try std.testing.expect(!run.hovered);    try std.testing.expect(stop.hovered);}test "Workspace retains frame storage at the high-water mark" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_highwater"),            null,            null,            null,            null,            null,            null,        );    }    var children: [16]UiNode = undefined;    for (&children, 0..) |*child, index| {        child.* = .{            .widget_id = index + 2,            .size = .{ .width = 4, .height = 4 },        };    }    var surface = UiSurfaceTree{        .available_size = .{ .width = 64, .height = 16 },        .root = .{            .widget_id = 1,            .style = .{ .flex_direction = .row },            .children = children[0..],        },    };    var workspace = Workspace.init(std.testing.allocator);    defer workspace.deinit();    const large = try workspace.buildSurface(&surface, .{});    const root_storage = large.roots.ptr;    const widget_storage = large.widgets.ptr;    surface.root.children = children[0..2];    const small = try workspace.buildSurface(&surface, .{});    try std.testing.expectEqual(@as(usize, 3), small.widgets.len);    try std.testing.expectEqual(root_storage, small.roots.ptr);    try std.testing.expectEqual(widget_storage, small.widgets.ptr);    surface.root.children = children[0..];    const regrown = try workspace.buildSurface(&surface, .{});    try std.testing.expectEqual(@as(usize, 17), regrown.widgets.len);    try std.testing.expectEqual(root_storage, regrown.roots.ptr);    try std.testing.expectEqual(widget_storage, regrown.widgets.ptr);}test "Workspace warmed builds need no backing allocation" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_steady_transitive_risk"),            null,            null,            null,            null,            null,            null,        );    }    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_steady_foreign_risk"),            null,            null,            null,            null,            null,            null,        );    }    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    const child = [_]UiNode{.{        .widget_id = 2,        .size = .{ .width = 8, .height = 8 },    }};    const surface = UiSurfaceTree{        .available_size = .{ .width = 16, .height = 16 },        .root = .{ .widget_id = 1, .children = child[0..] },    };    var workspace = Workspace.init(failing.allocator());    defer workspace.deinit();    for (0..3) |_| _ = try workspace.buildSurface(&surface, .{});    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    for (0..8) |_| {        const frame = try workspace.buildSurface(&surface, .{});        try std.testing.expectEqual(@as(usize, 2), frame.widgets.len);    }    try std.testing.expect(!failing.has_induced_failure);}test "Workspace remains reusable after allocation failure" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_oom"),            null,            null,            null,            null,            null,            null,        );    }    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });    const child = [_]UiNode{.{        .widget_id = 2,        .size = .{ .width = 8, .height = 8 },    }};    const surface = UiSurfaceTree{        .available_size = .{ .width = 16, .height = 16 },        .root = .{ .widget_id = 1, .children = child[0..] },    };    var workspace = Workspace.init(failing.allocator());    defer workspace.deinit();    try std.testing.expectError(error.OutOfMemory, workspace.buildSurface(&surface, .{}));    try std.testing.expect(workspace.storage == null);    failing.fail_index = std.math.maxInt(usize);    const frame = try workspace.buildSurface(&surface, .{});    try std.testing.expectEqual(@as(usize, 2), frame.widgets.len);}test "FrameStorage capacity matches an independent typed-byte model" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_capacity"),            null,            null,            null,            null,            null,            null,        );    }    const cases = [_]FrameStorage.Limits{        .{ .nodes = 1, .layout_nodes = 1, .children = 0, .roots = 1 },        .{ .nodes = 17, .layout_nodes = 9, .children = 8, .roots = 1 },        .{ .nodes = 4_096, .layout_nodes = 2_048, .children = 2_047, .roots = 8 },        .{ .nodes = std.math.maxInt(u32), .layout_nodes = std.math.maxInt(u32), .children = std.math.maxInt(u32), .roots = 64 },    };    for (cases) |limits| {        const derived = try FrameStorage.Capacity.derive(limits);        const modeled = try modelFrameCapacity(limits);        try std.testing.expectEqual(modeled, derived);    }    try std.testing.expectError(        error.CapacityOverflow,        FrameStorage.Capacity.derive(.{ .nodes = 0, .layout_nodes = 0, .children = 0, .roots = 1 }),    );    try std.testing.expectError(        error.CapacityOverflow,        FrameStorage.Capacity.derive(.{ .nodes = 1, .layout_nodes = 1, .children = 0, .roots = 0 }),    );}test "FrameStorage acquires the exact surveyed equation and seals" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_acquisition"),            null,            null,            null,            null,            null,            null,        );    }    var storage = try FrameStorage.init(std.testing.allocator, .{        .nodes = 5,        .layout_nodes = 3,        .children = 4,        .roots = 2,    });    defer storage.deinit(std.testing.allocator);    storage.activate();    try std.testing.expectEqual(@as(usize, 2), storage.roots.len);    try std.testing.expectEqual(@as(usize, 5), storage.widgets.len);    try std.testing.expectEqual(@as(usize, 4), storage.flex_nodes.len);    try std.testing.expectEqual(@as(usize, 4), storage.layout_scratch.results.len);    try std.testing.expectEqual(@as(usize, 4), storage.layout_scratch.metrics.len);    try std.testing.expectEqual(@as(usize, 4), storage.layout_scratch.lines.len);    try std.testing.expectEqual(@as(usize, 3), storage.layout_scratch.states.len);    try std.testing.expectEqual(@as(usize, 4), storage.layout_scratch.child_indices.len);    try std.testing.expect(storage.admits(.{ .nodes = 5, .layout_nodes = 3, .children = 4 }, 2));    try std.testing.expect(!storage.admits(.{ .nodes = 6, .layout_nodes = 3, .children = 4 }, 2));    try std.testing.expect(!storage.admits(.{ .nodes = 5, .layout_nodes = 4, .children = 4 }, 2));    try std.testing.expect(!storage.admits(.{ .nodes = 5, .layout_nodes = 3, .children = 5 }, 2));    try std.testing.expect(!storage.admits(.{ .nodes = 5, .layout_nodes = 3, .children = 4 }, 3));}test "surveySurface counts nodes and children exactly and rejects over-deep trees" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_survey"),            null,            null,            null,            null,            null,            null,        );    }    const grandchildren = [_]UiNode{        .{ .widget_id = 4 },        .{ .widget_id = 5 },    };    const children = [_]UiNode{        .{ .widget_id = 2, .children = grandchildren[0..] },        .{ .widget_id = 3 },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 64, .height = 64 },        .root = .{ .widget_id = 1, .children = children[0..] },    };    const facts = try surveySurface(&surface);    try std.testing.expectEqual(@as(usize, 5), facts.nodes);    try std.testing.expectEqual(@as(usize, 5), facts.layout_nodes);    try std.testing.expectEqual(@as(usize, 4), facts.children);    var spine: [max_depth + 1]UiNode = undefined;    spine[max_depth] = .{ .widget_id = max_depth };    var level: usize = max_depth;    while (level > 0) {        level -= 1;        spine[level] = .{            .widget_id = level,            .children = spine[level + 1 ..][0..1],        };    }    const deep = UiSurfaceTree{        .available_size = .{ .width = 8, .height = 8 },        .root = spine[0],    };    try std.testing.expectError(error.SurfaceTooDeep, surveySurface(&deep));}test "Session publishes double-buffered multi-root frames without reallocation" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_session"),            null,            null,            null,            null,            null,            null,        );    }    var session = Session.init(std.testing.allocator);    defer session.deinit();    const first_children = [_]UiNode{        .{ .widget_id = 2, .size = .{ .width = 8, .height = 8 } },    };    const first = UiSurfaceTree{        .available_size = .{ .width = 32, .height = 16 },        .root = .{ .widget_id = 1, .children = first_children[0..] },    };    const second = UiSurfaceTree{        .available_size = .{ .width = 16, .height = 16 },        .root = .{ .widget_id = 10 },    };    const facts = (try surveySurface(&first)).add(try surveySurface(&second));    _ = try session.begin(facts, 2);    try session.append(.{ .surface = &first, .root_id = 1, .revision = 7 }, .{});    try session.append(.{ .surface = &second, .root_id = 2, .revision = 7 }, .{});    const frame = session.publish(7);    try std.testing.expectEqual(@as(usize, 2), frame.root_count);    try std.testing.expectEqual(@as(usize, 3), frame.widget_count);    try std.testing.expectEqual(@as(u64, 1), frame.roots[0].root_id);    try std.testing.expectEqual(@as(u64, 2), frame.roots[1].root_id);    const published_widgets = frame.widgets.ptr;    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 });    _ = failing.allocator();    _ = try session.begin(facts, 2);    try session.append(.{ .surface = &first, .root_id = 1, .revision = 8 }, .{});    try session.append(.{ .surface = &second, .root_id = 2, .revision = 8 }, .{});    try std.testing.expectEqual(@as(u64, 1), frame.roots[0].root_id);    try std.testing.expectEqual(published_widgets, frame.widgets.ptr);    try std.testing.expect(!failing.has_induced_failure);    const next = session.publish(8);    try std.testing.expectEqual(@as(usize, 3), next.widget_count);    try std.testing.expect(next.widgets.ptr != published_widgets);}test "Session steady builds at admitted demand make no allocator calls" {    comptime {        @stardustClaim(            @import("alloc_phase").capacity.witness(FrameStorage, "gui_frame_session_steady"),            null,            null,            null,            null,            null,            null,        );    }    var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});    var session = Session.init(failing.allocator());    defer session.deinit();    const children = [_]UiNode{        .{ .widget_id = 2, .size = .{ .width = 8, .height = 8 } },        .{ .widget_id = 3, .size = .{ .width = 8, .height = 8 } },    };    const surface = UiSurfaceTree{        .available_size = .{ .width = 32, .height = 16 },        .root = .{ .widget_id = 1, .children = children[0..] },    };    const facts = try surveySurface(&surface);    for (0..2) |_| {        _ = try session.begin(facts, 1);        try session.append(.{ .surface = &surface, .root_id = 1, .revision = 1 }, .{});        _ = session.publish(1);    }    failing.fail_index = failing.alloc_index;    failing.resize_fail_index = failing.resize_index;    for (0..8) |iteration| {        _ = try session.begin(facts, 1);        try session.append(.{ .surface = &surface, .root_id = 1, .revision = iteration }, .{});        const frame = session.publish(iteration);        try std.testing.expectEqual(@as(usize, 3), frame.widget_count);    }    try std.testing.expect(!failing.has_induced_failure);}

Source: lib/gui/src/root.zig:11

zig
pub const frame = @import("frame.zig");

Complete caller list for frame.Workspace.buildSurface

11 direct callers.

Complete caller list for frame.Workspace.deinit

11 direct callers.

Complete caller list for frame.Workspace.init

11 direct callers.

Audit

Definitions35
Public names35
Members37
Version26.7.0
Revisiondaab053ee433