///|
pub(all) struct ViewIdentity {
kind : String
key : Key?
} derive(Eq)
///|
pub(all) struct ViewLayoutContext {
constraints : Constraints
child_sizes : Array[Size]
child_flex_weights : Array[Double]
/// Frames from the previous completed layout pass keyed by stable view key.
/// Runtime-owned layout code supplies this snapshot so presentation nodes can
/// resolve anchors without depending on the runtime package.
anchor_frames : Array[(String, Rect)]
state : ViewStateContext
text_system : TextSystem
environment : Environment
style : ViewStyle
}
///|
pub(all) struct ViewLayoutResult {
size : Size
child_frames : Array[Rect]
} derive(Eq, Debug, ToJson)
///|
/// Recursive layout tree produced by the layout pass. Unlike
/// `ViewLayoutResult` (a single node's measured size + child frames),
/// `LayoutResult` is the fully-nested tree emitted by `AppRuntime::layout`.
pub(all) struct LayoutResult {
frame : Rect
children : Array[LayoutResult]
} derive(Eq, Debug, ToJson)
///|
pub fn hit_test(layout : LayoutResult, point : Point) -> Bool {
if !layout.frame.contains(point) {
false
} else if layout.children.length() == 0 {
true
} else {
layout.children.any(child => hit_test(child, point))
}
}
///|
pub(all) struct ViewPaintContext {
frame : Rect
child_frames : Array[Rect]
focused : Bool
state : ViewStateContext
text_control : TextControlStateContext
environment : Environment
style : ViewStyle
text_system : TextSystem
/// Current animation clock time in milliseconds, threaded from the runtime's
/// `FrameTick`. Controls read this to compute transition `progress` between
/// a state-change timestamp and `now_ms`. Stays 0.0 when no clock is active
/// (e.g. the intrinsic-size layout path), which makes progress clamp to the
/// terminal state so non-animated paint is unchanged.
now_ms : Double
}
///|
/// Context for the platform-tree pass. Platform placements are produced
/// independently from paint so native child views can update without creating
/// draw commands or scheduling a renderer frame.
pub(all) struct ViewPlatformContext {
frame : Rect
child_frames : Array[Rect]
environment : Environment
style : ViewStyle
}
///|
pub(all) struct ViewPaintPlan {
commands : Array[DrawCommand]
child_layers : Array[ViewPaintLayer]
post_commands : Array[DrawCommand]
overlay_commands : Array[DrawCommand]
repaint_boundary : Bool
/// True when this paint pass is a mid-animation sample (progress < 1.0) and
/// the runtime should keep requesting redraws until the animation settles.
/// The runtime folds this across the painted tree to decide whether to keep
/// ticking. Controls that hard-cut between states leave this false.
animating : Bool
} derive(Eq, Debug)
///|
pub(all) struct ViewPaintLayer {
frame : Rect
cache_key : String
content_revision : Int
commands : Array[DrawCommand]
overlay_commands : Array[DrawCommand]
} derive(Eq, Debug)
///|
pub(all) struct ViewEventContext {
frame : Rect
child_frames : Array[Rect]
event : AppEvent
focused : Bool
state : ViewStateContext
text_control : TextControlStateContext
text_system : TextSystem
environment : Environment
style : ViewStyle
/// Current animation clock time in ms, matching `ViewPaintContext.now_ms`.
/// Event handlers read this to timestamp state transitions (e.g. recording
/// when a button entered hover) so the next paint can interpolate progress.
now_ms : Double
}
///|
pub(all) enum ViewDirtyHint {
ViewClean
ViewPaintDirty
ViewLayoutDirty
ViewBuildDirty
} derive(Eq, Debug)
///|
pub(all) struct ViewEventResult[Msg] {
changed : Bool
activated : Bool
focused : Bool
captured : Bool
state : ViewStateContext?
text_control : TextControlStateContext?
dirty : ViewDirtyHint
messages : Array[Msg]
} derive(Eq, Debug)
///|
pub(all) struct ViewTextCommandResult[Msg] {
result : TextCommandResult
text_control : TextControlStateContext?
messages : Array[Msg]
} derive(Eq, Debug)
///|
pub(all) struct ViewSemanticsInfo {
composition : SemanticsComposition
semantic_id : SemanticId?
role : SemanticsRole?
level : Int?
url : String?
label : String?
value : String?
description : String?
disabled : Bool?
selected : Bool?
pressed : Bool?
checked : SemanticsCheckedState?
expanded : Bool?
invalid : Bool?
required : Bool?
read_only : Bool?
busy : Bool?
multiline : Bool?
password : Bool?
modal : Bool?
numeric : SemanticsNumericValue?
text : SemanticsTextValue?
collection : SemanticsCollectionInfo?
relations : SemanticsRelationsDeclaration?
live : SemanticsLive?
live_atomic : Bool?
} derive(Eq, Debug, ToJson)
///|
pub(all) struct ViewStateContext {
focused : Bool
hovered : Bool
pressed : Bool
selected : Bool
checked : Bool
expanded : Bool
disabled : Bool
slots : ViewStateSlots
} derive(Eq, Debug)
///|
pub(all) struct TextHistoryEntryContext {
text : String
caret : Int
selection : TextRange?
} derive(Eq, Debug, ToJson)
///|
pub(all) struct TextControlStateContext {
caret : Int
selection : TextRange?
text_selection_anchor : Int?
composition : String?
composition_cursor : TextRange?
undo_stack : Array[TextHistoryEntryContext]
redo_stack : Array[TextHistoryEntryContext]
} derive(Eq, Debug, ToJson)
///|
pub fn ViewIdentity::new(kind~ : String, key? : String? = None) -> ViewIdentity {
{
kind,
key: match key {
Some(value) => Some(Key::new(value))
None => None
},
}
}
///|
pub fn ViewLayoutResult::new(
size~ : Size,
child_frames? : Array[Rect] = [],
) -> ViewLayoutResult {
{ size, child_frames }
}
///|
pub fn ViewPaintPlan::empty() -> ViewPaintPlan {
{
commands: [],
child_layers: [],
post_commands: [],
overlay_commands: [],
repaint_boundary: false,
animating: false,
}
}
///|
pub fn ViewPaintPlan::commands(commands : Array[DrawCommand]) -> ViewPaintPlan {
{
commands,
child_layers: [],
post_commands: [],
overlay_commands: [],
repaint_boundary: false,
animating: false,
}
}
///|
/// Like `ViewPaintPlan::commands` but flags the paint as a mid-animation
/// sample. The runtime keeps requesting redraws while any painted plan in the
/// tree reports `animating=true`. Controls set this when their transition
/// `progress < 1.0` (and `reduced_motion` is not in effect).
pub fn ViewPaintPlan::animating_commands(
commands : Array[DrawCommand],
) -> ViewPaintPlan {
{ ..ViewPaintPlan::commands(commands), animating: true }
}
///|
pub fn ViewPaintLayer::new(
frame~ : Rect,
cache_key~ : String,
content_revision~ : Int,
commands? : Array[DrawCommand] = [],
overlay_commands? : Array[DrawCommand] = [],
) -> ViewPaintLayer {
{ frame, cache_key, content_revision, commands, overlay_commands }
}
///|
pub fn[Msg] ViewEventResult::ignored() -> ViewEventResult[Msg] {
{
changed: false,
activated: false,
focused: false,
captured: false,
state: None,
text_control: None,
dirty: ViewClean,
messages: [],
}
}
///|
pub fn[Msg] ViewEventResult::message(message : Msg) -> ViewEventResult[Msg] {
{
changed: true,
activated: true,
focused: false,
captured: true,
state: None,
text_control: None,
dirty: ViewPaintDirty,
messages: [message],
}
}
///|
pub fn[Msg] ViewTextCommandResult::ignored() -> ViewTextCommandResult[Msg] {
{ result: TextCommandResult::ignored(), text_control: None, messages: [] }
}
///|
pub fn[Msg] ViewTextCommandResult::new(
result~ : TextCommandResult,
text_control? : TextControlStateContext? = None,
messages? : Array[Msg] = [],
) -> ViewTextCommandResult[Msg] {
{ result, text_control, messages }
}
///|
pub fn ViewSemanticsInfo::new(
composition? : SemanticsComposition = SemanticsComposition::Transparent,
semantic_id? : SemanticId,
role? : SemanticsRole,
level? : Int,
url? : String,
label? : String,
value? : String,
description? : String,
disabled? : Bool,
selected? : Bool,
pressed? : Bool,
checked? : SemanticsCheckedState,
expanded? : Bool,
invalid? : Bool,
required? : Bool,
read_only? : Bool,
busy? : Bool,
multiline? : Bool,
password? : Bool,
modal? : Bool,
numeric? : SemanticsNumericValue,
text? : SemanticsTextValue,
collection? : SemanticsCollectionInfo,
relations? : SemanticsRelationsDeclaration,
live? : SemanticsLive,
live_atomic? : Bool,
) -> ViewSemanticsInfo {
{
composition,
semantic_id,
role,
level,
url,
label,
value,
description,
disabled,
selected,
pressed,
checked,
expanded,
invalid,
required,
read_only,
busy,
multiline,
password,
modal,
numeric,
text,
collection,
relations,
live,
live_atomic,
}
}
///|
pub struct View[Msg] {
priv adapter : ViewAdapter[Msg]
}
///|
/// Construct a typed view from a concrete message-independent node.
///
/// The node supplies layout, paint, semantics, focus, and reconciliation
/// behavior through `ViewNode`. Optional adapters attach typed children,
/// events, and text commands without erasing `Msg` from the
/// public view. Identity, declaration, semantics metadata, and static children
/// are sampled exactly once here. `View::map` maps only typed adapters and
/// preserves those frozen snapshots.
pub fn[Node : ViewNode, Msg] View::from_node(
node : Node,
children? : (Node) -> Array[View[Msg]] = _node => [],
event? : (Node, ViewEventContext) -> ViewEventResult[Msg] = (_node, _context) => {
ViewEventResult::ignored()
},
text_command? : (Node, ViewEventContext, CommandIntent, String?) -> ViewTextCommandResult[
Msg,
] = (_node, _context, _intent, _paste_text) => {
ViewTextCommandResult::ignored()
},
semantics_actions? : (Node) -> Array[ViewSemanticsActionHandler[Msg]] = _node => {
[]
},
) -> View[Msg] {
let identity_snapshot = node.identity()
let declaration_snapshot = node.declaration()
let semantics_snapshot = node.semantics()
let focusable_snapshot = node.focusable()
let focus_trap_snapshot = node.focus_trap()
let semantics_action_handlers = semantics_actions(node)
let children_snapshot = children(node)
{
adapter: ViewAdapter::new(
node=node as &ViewNode,
identity=identity_snapshot,
declaration=declaration_snapshot,
semantics=semantics_snapshot,
focusable=focusable_snapshot,
focus_trap=focus_trap_snapshot,
semantics_action_handlers~,
children=children_snapshot,
event=context => event(node, context),
text_command=(context, intent, paste_text) => {
text_command(node, context, intent, paste_text)
},
),
}
}
///|
pub fn[Msg] View::identity(self : View[Msg]) -> ViewIdentity {
self.adapter.identity_snapshot
}
///|
pub fn[Msg] View::declaration(self : View[Msg]) -> ViewDeclaration {
self.adapter.declaration_snapshot
}
///|
pub fn[Msg] View::children(self : View[Msg]) -> Array[View[Msg]] {
self.adapter.children()
}
///|
pub fn[Msg] View::child_constraints(
self : View[Msg],
constraints : Constraints,
) -> Constraints {
self.adapter.node.child_constraints(constraints)
}
///|
pub fn[Msg] View::child_environment(
self : View[Msg],
environment : Environment,
) -> Environment {
self.adapter.node.child_environment(environment)
}
///|
pub fn[Msg] View::child_style(self : View[Msg], style : ViewStyle) -> ViewStyle {
self.adapter.node.child_style(style)
}
///|
pub fn[Msg] View::layout(
self : View[Msg],
context : ViewLayoutContext,
) -> ViewLayoutResult {
self.adapter.node.layout(context)
}
///|
pub fn[Msg] View::paint(
self : View[Msg],
context : ViewPaintContext,
) -> ViewPaintPlan {
self.adapter.node.paint(context)
}
///|
pub fn[Msg] View::platform_views(
self : View[Msg],
context : ViewPlatformContext,
) -> Array[PlatformViewPlacement] {
self.adapter.node.platform_views(context)
}
///|
pub fn[Msg] View::event(
self : View[Msg],
context : ViewEventContext,
) -> ViewEventResult[Msg] {
self.adapter.event(context)
}
///|
pub fn[Msg] View::text_input_state(
self : View[Msg],
context : ViewPaintContext,
) -> TextInputState? {
self.adapter.node.text_input_state(context)
}
///|
pub fn[Msg] View::text_command(
self : View[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> TextCommandResult {
self.adapter.text_command_result(context, intent, paste_text~).result
}
///|
pub fn[Msg] View::text_command_result(
self : View[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> ViewTextCommandResult[Msg] {
self.adapter.text_command_result(context, intent, paste_text~)
}
///|
pub fn[Msg] View::semantics(self : View[Msg]) -> ViewSemanticsInfo {
self.adapter.semantics_snapshot
}
///|
pub fn[Msg] View::accepts_focus(self : View[Msg]) -> Bool {
self.adapter.focusable_snapshot
}
///|
pub fn[Msg] View::traps_focus(self : View[Msg]) -> Bool {
self.adapter.focus_trap_snapshot
}
///|
pub fn[Msg] View::semantics_action_kinds(
self : View[Msg],
) -> Array[SemanticsActionKind] {
self.adapter.semantics_action_kinds()
}
///|
pub fn[Msg] View::dispatch_semantics_action(
self : View[Msg],
context : ViewSemanticsActionContext,
action : SemanticsAction,
) -> ViewSemanticsActionResult[Msg] {
self.adapter.dispatch_semantics_action(context, action)
}
///|
pub fn[Msg] View::flex_weight(self : View[Msg]) -> Double {
self.adapter.node.flex_weight()
}