///|
priv struct RuntimeState {
mut element_tree : ElementTree
mut layout_tree : LayoutTree
anchor_registry : AnchorFrameRegistry
mut render_tree : RenderTree
mut platform_tree : PlatformTree
mut size : Size
dirty : DirtyTracking
stats : RenderStats
mut next_element_id : Int
animfocus : AnimFocusState
mut environment : Environment
mut text_system : TextSystem
mut redraw_callback : (() -> Unit)?
mut action_commands : ActionCommandMap
begin_message_batch : () -> Unit
end_message_batch : () -> Unit
mut event_observer : (AppEvent) -> Unit
mut root_builder : (() -> &RuntimeViewNode)?
clock : Clock
mut pending_scroll_request : ScrollRequest?
mut last_event_hit_path : Array[Int]
mut last_frame_timing : RuntimeFrameTiming
/// Post-layout overlay frame overrides: host element id -> (child index,
/// absolute rect). Applied by the placement pass after the layout fixpoint.
mut overlay_overrides : Array[(Int, Array[(Int, Rect)])]
/// Keyboard dismissal layer stack, back-to-front. The top-most entry
/// receives Escape/Back before in-flow content (ADR 0033).
mut layer_stack : Array[ElementId]
/// Exiting overlay subtrees (ADR 0033): removed presentations/popups are
/// replayed as fading command snapshots until their transition completes.
exiting_overlays : Array[ExitingOverlay]
/// Element ids of overlay-positioned surfaces active in the last placement
/// pass; drives exit-retention detection.
mut active_surface_ids : Array[ElementId]
/// First-seen frame-clock time per active overlay surface id; drives enter
/// fades.
overlay_enter_times : Array[(ElementId, Double)]
mut closed : Bool
semantics : SemanticsSubsystem
}
///|
priv struct ScrollRequest {
key : String
alignment : ScrollAlignment
}
///|
fn[Msg] RuntimeState::new_view(
root~ : @core.View[Msg],
size~ : Size,
environment? : Environment = Environment::neutral(),
on_message? : ((Msg) -> Unit)? = None,
begin_message_batch? : () -> Unit = () => (),
end_message_batch? : () -> Unit = () => (),
) -> RuntimeState {
let emit = match on_message {
Some(handler) => handler
None => _message => ()
}
RuntimeState::new_runtime_view_node(
root=adapt_view_for_runtime(root, emit),
size~,
environment~,
begin_message_batch~,
end_message_batch~,
)
}
///|
fn RuntimeState::new_runtime_view_node(
root~ : &RuntimeViewNode,
size~ : Size,
environment? : Environment = Environment::neutral(),
begin_message_batch? : () -> Unit = () => (),
end_message_batch? : () -> Unit = () => (),
) -> RuntimeState {
let initial = mount_runtime_view_node_with_next(root, 1)
let state = {
element_tree: ElementTree::new(initial.node),
layout_tree: LayoutTree::empty(),
anchor_registry: AnchorFrameRegistry::new(),
render_tree: RenderTree::empty(),
platform_tree: PlatformTree::empty(),
size,
dirty: DirtyTracking::new(),
stats: RenderStats::new(),
next_element_id: initial.next_id,
animfocus: AnimFocusState::new(),
environment,
text_system: TextSystem::fallback(),
redraw_callback: None,
action_commands: ActionCommandMap::new(),
begin_message_batch,
end_message_batch,
event_observer: _event => (),
root_builder: None,
clock: Clock::new(),
pending_scroll_request: None,
last_event_hit_path: [],
last_frame_timing: RuntimeFrameTiming::zero(),
overlay_overrides: [],
layer_stack: [],
exiting_overlays: [],
active_surface_ids: [],
overlay_enter_times: [],
closed: false,
semantics: SemanticsSubsystem::new(),
}
state.mark_dirty(state.element_tree.id())
state.render_current_tree()
state
}
///|
fn RuntimeState::environment(self : RuntimeState) -> Environment {
self.environment
}
///|
fn RuntimeState::text_system(self : RuntimeState) -> TextSystem {
self.text_system
}
///|
fn RuntimeState::set_text_system(
self : RuntimeState,
text_system : TextSystem,
) -> Unit {
self.text_system = text_system
self.dirty.needs_layout = true
self.dirty.needs_paint = true
self.dirty.needs_redraw = true
self.element_tree.root.mark_subtree_layout_dirty()
self.mark_layout_dirty(self.element_tree.id())
}
///|
fn RuntimeState::register_font_data(
self : RuntimeState,
family : FontFamily,
data : Bytes,
) -> Unit {
self.text_system.register_font_data(family, data)
self.dirty.needs_layout = true
self.dirty.needs_paint = true
self.dirty.needs_redraw = true
self.element_tree.root.mark_subtree_layout_dirty()
self.mark_layout_dirty(self.element_tree.id())
}
///|
fn RuntimeState::set_environment(
self : RuntimeState,
environment : Environment,
) -> Unit {
if self.environment != environment {
self.environment = environment
self.request_root_rebuild(reason="environment changed")
self.dirty.needs_layout = true
self.dirty.needs_paint = true
self.dirty.needs_redraw = true
self.element_tree.root.mark_subtree_layout_dirty()
self.mark_build_dirty(self.element_tree.id())
}
}
///|
fn RuntimeState::request_destroy(
self : RuntimeState,
destroy_program : () -> Unit,
) -> Unit {
if self.semantics.semantics_action_dispatching {
self.semantics.semantics_destroy_requested = true
self.semantics.semantics_destroy_program = Some(destroy_program)
} else {
destroy_program()
self.destroy_now()
}
}
///|
fn RuntimeState::discard_semantics_destroy_request(self : RuntimeState) -> Unit {
self.semantics.semantics_destroy_requested = false
self.semantics.semantics_destroy_program = None
}
///|
fn RuntimeState::complete_semantics_destroy_request(
self : RuntimeState,
) -> Bool {
if !self.semantics.semantics_destroy_requested {
return false
}
let destroy_program = self.semantics.semantics_destroy_program
self.discard_semantics_destroy_request()
match destroy_program {
Some(destroy) => destroy()
None => ()
}
self.destroy_now()
true
}
///|
fn RuntimeState::destroy_now(self : RuntimeState) -> Unit {
self.element_tree = ElementTree::empty()
self.layout_tree = LayoutTree::empty()
self.anchor_registry.clear()
self.render_tree = RenderTree::empty()
self.platform_tree = PlatformTree::empty()
self.next_element_id = 1
self.dirty.clear()
self.animfocus.focused_element = None
self.animfocus.active_pointer_capture = None
self.animfocus.active_focus_trap = None
self.animfocus.saved_focus_key = None
self.animfocus.saved_focus_semantic_id = None
self.animfocus.saved_focus_order = None
self.pending_scroll_request = None
self.stats.rebuild_pass_count = 0
self.stats.layout_pass_count = 0
self.stats.paint_pass_count = 0
self.stats.semantics_recompute_count = 0
self.stats.draw_command_build_count = 0
self.stats.last_damage_region = DamageRegion::empty()
self.closed = true
self.semantics.semantics_tree = SemanticsTree::empty()
self.semantics.committed_semantics = SemanticsSnapshot::empty()
self.semantics.semantics_initialized = false
self.semantics.semantics_journal.clear()
self.semantics.semantic_id_index.clear()
self.semantics.semantics_commit_in_progress = false
self.semantics.semantics_action_dispatching = false
self.discard_semantics_destroy_request()
}
///|
fn RuntimeState::set_redraw_callback(
self : RuntimeState,
callback : () -> Unit,
) -> Unit {
self.redraw_callback = Some(callback)
}
///|
fn RuntimeState::layout(self : RuntimeState) -> LayoutResult {
self.ensure_layout_tree()
self.layout_tree.root.to_layout_result()
}
///|
fn RuntimeState::cached_layout(self : RuntimeState) -> LayoutResult {
self.layout_tree.root.to_layout_result()
}
///|
fn RuntimeState::cached_layout_tree_root(self : RuntimeState) -> PlacedNode {
self.layout_tree.root
}
///|
fn RuntimeState::view_tree_inspector_snapshot(
self : RuntimeState,
) -> ViewTreeInspectorSnapshot {
self.element_tree.root.view_tree_inspector_snapshot()
}
///|
fn RuntimeState::cached_semantics(self : RuntimeState) -> SemanticsSnapshot {
self.commit_semantics()
self.semantics.committed_semantics
}
///|
fn RuntimeState::inspector_rebuild_reason(self : RuntimeState) -> String {
if self.dirty.needs_rebuild {
"rebuild requested"
} else if self.dirty.needs_layout {
"layout requested"
} else if self.dirty.needs_paint {
"paint requested"
} else if self.dirty.needs_redraw {
"redraw requested"
} else {
"clean"
}
}
///|
fn RuntimeState::inspector_dirty_reasons(self : RuntimeState) -> Array[String] {
let reasons : Array[String] = []
if self.dirty.needs_rebuild {
reasons.push("rebuild")
}
if self.dirty.needs_layout {
reasons.push("layout")
}
if self.dirty.needs_paint {
reasons.push("paint")
}
if self.dirty.needs_redraw {
reasons.push("redraw")
}
if self.dirty.dirty_elements.length() > 0 {
reasons.push("dirty elements: \{self.dirty.dirty_elements.length()}")
}
reasons
}
///|
fn RuntimeState::focused_text_input(self : RuntimeState) -> TextInputState {
self.ensure_render_tree()
match self.animfocus.focused_element {
Some(target) =>
match
self.element_tree.root.focused_text_input(
target,
self.render_tree.root,
text_system=self.text_system,
environment=self.environment,
) {
Some(state) => state
None => TextInputState::none()
}
None => TextInputState::none()
}
}
///|
fn RuntimeState::build_draw_commands(self : RuntimeState) -> Array[DrawCommand] {
self.stats.draw_command_build_count = self.stats.draw_command_build_count + 1
let commands : Array[DrawCommand] = Array(
capacity=self.stats.last_command_count.max(64),
)
commands.push(Clear(Color::white()))
self.rebuild_if_possible()
self.ensure_render_tree()
self.render_tree.root.collect_draw_commands(commands)
self.append_exiting_overlay_commands(commands)
self.stats.last_command_count = commands.length()
commands
}
///|
fn RuntimeState::build_draw_frame(self : RuntimeState) -> DrawFrame {
self.stats.draw_command_build_count = self.stats.draw_command_build_count + 1
let commands : Array[DrawCommand] = Array(
capacity=self.stats.last_command_count.max(64),
)
self.rebuild_if_possible()
self.ensure_render_tree()
self.render_tree.root.collect_frame_commands(commands)
self.append_exiting_overlay_commands(commands)
let overlay_bounds = self.render_tree.root.overlay_bounds()
let platform_views = self.platform_tree.placements()
let transparent_platform_frame = (
!platform_views.is_empty() && overlay_bounds is Some(_)
) ||
platform_views.any(placement => {
platform_view_covers_surface(placement, self.size)
})
let clear_color = if transparent_platform_frame {
Color::rgba(r=0.0, g=0.0, b=0.0, a=0.0)
} else {
Color::white()
}
self.stats.last_command_count = commands.length()
// Ship the union of damage across every paint pass since the last frame
// build (event handling can paint mid-batch), then start accumulating for
// the next frame.
let damage = self.dirty.accumulated_damage
self.dirty.accumulated_damage = DamageRegion::empty()
DrawFrame::new(
commands~,
platform_views~,
overlay_bounds~,
damage~,
clear_color~,
)
}
///|
/// A full-surface native view owns the ordinary frame background. Keeping the
/// renderer clear transparent lets a presenter remain above that view without
/// obscuring it, so modal overlay state does not require native sibling churn.
fn platform_view_covers_surface(
placement : PlatformViewPlacement,
surface_size : Size,
) -> Bool {
let frame = placement.frame
let covers_surface = frame.origin.x <= 0.0 &&
frame.origin.y <= 0.0 &&
frame.max_x() >= surface_size.width &&
frame.max_y() >= surface_size.height
let clip_covers_surface = match placement.clip {
Some(clip) =>
clip.origin.x <= 0.0 &&
clip.origin.y <= 0.0 &&
clip.max_x() >= surface_size.width &&
clip.max_y() >= surface_size.height
None => true
}
covers_surface && clip_covers_surface
}
///|
fn RuntimeState::cached_draw_commands(
self : RuntimeState,
) -> Array[DrawCommand] {
let commands : Array[DrawCommand] = [Clear(Color::white())]
self.render_tree.root.collect_draw_commands(commands)
commands
}
///|
fn RuntimeState::rebuild_if_possible(self : RuntimeState) -> Unit {
if self.dirty.needs_rebuild {
match self.root_builder {
Some(build) => {
self.stats.rebuild_pass_count = self.stats.rebuild_pass_count + 1
self.capture_focus_restore_candidate_before_rebuild()
let result = reconcile_element_for_runtime(
self.element_tree.root,
build(),
Some(self),
self.next_element_id,
)
self.element_tree = ElementTree::new(result.node)
self.next_element_id = result.next_id
self.sync_focus_trap_after_rebuild()
self.clear_stale_pointer_capture()
self.mark_current_dirty_elements()
}
None => ()
}
self.dirty.needs_rebuild = false
self.dirty.dirty_build_elements.clear()
self.refresh_dirty_element_queue()
}
}
///|
fn RuntimeState::set_root_builder(
self : RuntimeState,
builder : () -> &RuntimeViewNode,
) -> Unit {
self.root_builder = Some(builder)
}
///|
fn RuntimeState::request_root_rebuild(
self : RuntimeState,
reason? : String = "program model changed",
) -> Unit {
if self.root_builder is Some(_) {
self.dirty.needs_rebuild = true
self.dirty.full_damage_reason = Some(reason)
self.queue_dirty_element(self.element_tree.id())
self.queue_build_dirty(self.element_tree.id())
self.request_rebuild_wakeup()
}
}
///|
fn RuntimeState::request_activation_rebuild(self : RuntimeState) -> Unit {
self.request_root_rebuild(reason="activation rebuild")
}
///|
/// Insertion-ordered element-id set backing the per-phase dirty queues.
/// The map gives O(1) dedup where `push_unique_element_id` previously
/// scanned the array (`Array.any`, O(n²) per queue fold on full-tree
/// invalidation); the array preserves queue order for phase processing.
priv struct DirtyIdSet {
items : Array[ElementId]
seen : Map[Int, Unit]
}
///|
fn DirtyIdSet::new() -> DirtyIdSet {
{ items: [], seen: Map([]), }
}
///|
fn DirtyIdSet::push(self : DirtyIdSet, id : ElementId) -> Unit {
let key = id.value()
if !self.seen.contains(key) {
self.seen[key] = ()
self.items.push(id)
}
}
///|
fn DirtyIdSet::clear(self : DirtyIdSet) -> Unit {
self.items.clear()
self.seen.clear()
}
///|
fn DirtyIdSet::length(self : DirtyIdSet) -> Int {
self.items.length()
}
///|
fn DirtyIdSet::is_empty(self : DirtyIdSet) -> Bool {
self.items.is_empty()
}
///|
fn DirtyIdSet::to_array(self : DirtyIdSet) -> Array[ElementId] {
self.items.copy()
}
///|
/// Subsystem: combined dirty flags + per-phase element queues.
priv struct DirtyTracking {
mut needs_redraw : Bool
mut needs_rebuild : Bool
mut needs_layout : Bool
mut needs_paint : Bool
mut needs_semantics : Bool
mut needs_platform : Bool
// Rebuilding can remove pixels that no current render node covers. Keep a
// full-surface damage request until the rebuilt tree has been painted.
mut full_damage_reason : String?
// Union of damage from every paint pass since the last frame build. Events
// can paint (via `ensure_render_tree`) before the host asks for a frame;
// shipping only the latest pass's damage would leave earlier changes —
// e.g. a scrolled image's moved pixels — outside the rendered clip.
mut accumulated_damage : DamageRegion
mut rebuild_wakeup_redraw : Bool
dirty_elements : DirtyIdSet
dirty_build_elements : DirtyIdSet
dirty_layout_elements : DirtyIdSet
dirty_paint_elements : DirtyIdSet
dirty_semantics_elements : DirtyIdSet
dirty_platform_elements : DirtyIdSet
}
///|
/// Subsystem: semantics tree, snapshot, journal, and action-safe destroy lifecycle.
priv struct SemanticsSubsystem {
mut semantics_tree : SemanticsTree
mut committed_semantics : SemanticsSnapshot
mut semantics_initialized : Bool
semantics_journal : Array[SemanticsDelta]
mut next_semantics_node_id : UInt64
semantic_id_index : Map[SemanticId, SemanticIdIndexEntry]
mut semantics_commit_in_progress : Bool
mut semantics_action_dispatching : Bool
mut semantics_destroy_requested : Bool
mut semantics_destroy_program : (() -> Unit)?
}
///|
/// Subsystem: animation clock, active-animation latch, focus and pointer capture.
priv struct AnimFocusState {
mut now_ms : Double
mut active_animations : Bool
mut focused_element : ElementId?
mut semantic_focused : SemanticsNodeId?
mut active_pointer_capture : ElementId?
mut active_focus_trap : ElementId?
mut saved_focus_key : String?
mut saved_focus_semantic_id : SemanticId?
mut saved_focus_order : Int?
}
///|
/// Subsystem: render counters and damage bookkeeping.
priv struct RenderStats {
mut rebuild_pass_count : Int
mut layout_pass_count : Int
mut paint_pass_count : Int
mut semantics_recompute_count : Int
mut draw_command_build_count : Int
mut last_command_count : Int
mut last_damage_region : DamageRegion
}
///|
fn DirtyTracking::new() -> DirtyTracking {
{
needs_redraw: true,
needs_rebuild: false,
needs_layout: true,
needs_paint: true,
needs_semantics: true,
needs_platform: true,
full_damage_reason: None,
accumulated_damage: DamageRegion::empty(),
rebuild_wakeup_redraw: false,
dirty_elements: DirtyIdSet::new(),
dirty_build_elements: DirtyIdSet::new(),
dirty_layout_elements: DirtyIdSet::new(),
dirty_paint_elements: DirtyIdSet::new(),
dirty_semantics_elements: DirtyIdSet::new(),
dirty_platform_elements: DirtyIdSet::new(),
}
}
///|
/// Full reset of all dirty flags and queues. Reset semantics only: safe at
/// teardown (`destroy_now`) or whole-tree reset. Never call mid-frame — the
/// phase passes (`layout_current_tree` / `render_current_tree` /
/// `refresh_platform_tree`) own incremental clearing, and a blanket clear
/// would drop pending work (e.g. `needs_paint` while `needs_rebuild` is
/// still outstanding → stale rendering).
fn DirtyTracking::clear(self : DirtyTracking) -> Unit {
self.needs_redraw = false
self.needs_rebuild = false
self.needs_layout = false
self.needs_paint = false
self.needs_semantics = false
self.needs_platform = false
self.full_damage_reason = None
self.accumulated_damage = DamageRegion::empty()
self.rebuild_wakeup_redraw = false
self.dirty_elements.clear()
self.dirty_build_elements.clear()
self.dirty_layout_elements.clear()
self.dirty_paint_elements.clear()
self.dirty_semantics_elements.clear()
self.dirty_platform_elements.clear()
}
///|
fn RenderStats::new() -> RenderStats {
{
rebuild_pass_count: 0,
layout_pass_count: 0,
paint_pass_count: 0,
semantics_recompute_count: 0,
draw_command_build_count: 0,
last_command_count: 0,
last_damage_region: DamageRegion::empty(),
}
}
///|
fn AnimFocusState::new() -> AnimFocusState {
{
now_ms: 0.0,
active_animations: false,
focused_element: None,
semantic_focused: None,
active_pointer_capture: None,
active_focus_trap: None,
saved_focus_key: None,
saved_focus_semantic_id: None,
saved_focus_order: None,
}
}
///|
fn SemanticsSubsystem::new() -> SemanticsSubsystem {
{
semantics_tree: SemanticsTree::empty(),
committed_semantics: SemanticsSnapshot::empty(),
semantics_initialized: false,
semantics_journal: [],
next_semantics_node_id: 1UL,
semantic_id_index: Map([]),
semantics_commit_in_progress: false,
semantics_action_dispatching: false,
semantics_destroy_requested: false,
semantics_destroy_program: None,
}
}
///|
/// Subsystem migration path:
/// 1. Swap `RuntimeState` fields to `mut dirty: DirtyTracking` etc.
/// 2. Compiler errors guide ~400 `self.needs_*` → `self.dirty.needs_*` renames.
/// 3. Each rename is a `replace_all_preview` → `multi_edit` batch per file.
/// Constructors above produce correct initial values for the subsystem fields.
///|
/// A removed overlay subtree replayed as a fading command snapshot while its
/// exit transition runs.
priv struct ExitingOverlay {
commands : Array[DrawCommand]
started_ms : Double
duration_ms : Double
}
///|
/// Fade and replay exiting overlay snapshots; prune completed ones. While any
/// snapshot remains, the runtime keeps ticking frames so the fade animates.
fn RuntimeState::append_exiting_overlay_commands(
self : RuntimeState,
commands : Array[DrawCommand],
) -> Unit {
if self.exiting_overlays.is_empty() {
return
}
let now_ms = self.animfocus.now_ms
let mut index = 0
while index < self.exiting_overlays.length() {
let ghost = self.exiting_overlays[index]
let elapsed = now_ms - ghost.started_ms
if elapsed >= ghost.duration_ms {
ignore(self.exiting_overlays.remove(index))
} else {
let progress = if ghost.duration_ms > 0.0 {
(elapsed / ghost.duration_ms).clamp(min=0.0, max=1.0)
} else {
1.0
}
commands.push(PushOpacity(1.0 - progress))
for command in ghost.commands {
commands.push(command)
}
commands.push(PopOpacity)
index = index + 1
}
}
if !self.exiting_overlays.is_empty() {
self.animfocus.active_animations = true
}
}