///|
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]
text_system : TextSystem
environment : Environment
style : ViewStyle
}
///|
pub(all) struct ViewLayoutResult {
size : Size
child_frames : Array[Rect]
} derive(Eq, Debug)
///|
/// 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
}
///|
pub(all) struct ViewPaintPlan {
commands : Array[DrawCommand]
child_layers : Array[ViewPaintLayer]
post_commands : Array[DrawCommand]
overlay_commands : Array[DrawCommand]
platform_views : Array[PlatformViewPlacement]
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]
platform_views : Array[PlatformViewPlacement]
} 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 {
role : SemanticsRole
level : Int?
url : String
label : String
value : String
description : String
actions : Array[SemanticsAction]
disabled : Bool?
selected : Bool?
pressed : Bool?
checked : Bool?
expanded : Bool?
invalid : Bool?
required : Bool?
} derive(Eq, Debug)
///|
pub(all) struct ViewStateContext {
focused : Bool
hovered : Bool
pressed : Bool
selected : Bool
checked : Bool
expanded : Bool
disabled : Bool
scratch : String
} 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)
///|
priv struct ViewNode[Msg] {
identity_fn : () -> ViewIdentity
revision_fn : () -> String
// paint_revision 变化只触发 paint dirty(重算 paint + 清 render cache),
// 不触发 layout dirty(不清 layout cache、不重算 layout)。
// 用于滚动等"只影响 paint 不影响 layout"的场景。
paint_revision_fn : () -> String
children_fn : () -> Array[View[Msg]]
build_child_fn : ((ComponentContext) -> View[Msg])?
child_constraints_fn : (Constraints) -> Constraints
child_environment_fn : (Environment) -> Environment
child_style_fn : (ViewStyle) -> ViewStyle
layout_fn : (ViewLayoutContext) -> ViewLayoutResult
paint_fn : (ViewPaintContext) -> ViewPaintPlan
event_fn : (ViewEventContext) -> ViewEventResult[Msg]
text_input_state_fn : (ViewPaintContext) -> TextInputState?
text_command_fn : (ViewEventContext, CommandIntent, String?) -> ViewTextCommandResult[
Msg,
]
semantics_fn : () -> ViewSemanticsInfo
focusable_fn : () -> Bool
flex_weight_fn : () -> Double
}
///|
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: [],
platform_views: [],
repaint_boundary: false,
animating: false,
}
}
///|
pub fn ViewPaintPlan::commands(commands : Array[DrawCommand]) -> ViewPaintPlan {
{
commands,
child_layers: [],
post_commands: [],
overlay_commands: [],
platform_views: [],
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] = [],
platform_views? : Array[PlatformViewPlacement] = [],
) -> ViewPaintLayer {
{
frame,
cache_key,
content_revision,
commands,
overlay_commands,
platform_views,
}
}
///|
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(
role? : SemanticsRole = SemanticsRole::Group,
level? : Int,
url? : String = "",
label? : String = "",
value? : String = "",
description? : String = "",
actions? : Array[SemanticsAction] = [],
disabled? : Bool,
selected? : Bool,
pressed? : Bool,
checked? : Bool,
expanded? : Bool,
invalid? : Bool,
required? : Bool,
) -> ViewSemanticsInfo {
{
role,
level,
url,
label,
value,
description,
actions,
disabled,
selected,
pressed,
checked,
expanded,
invalid,
required,
}
}
///|
fn[Msg] ViewNode::new(
identity~ : () -> ViewIdentity,
revision? : () -> String = () => "",
paint_revision? : () -> String = () => "",
children? : () -> Array[View[Msg]] = () => [],
build_child? : ((ComponentContext) -> View[Msg])? = None,
child_constraints? : (Constraints) -> Constraints = constraints => {
constraints.loosen()
},
child_environment? : (Environment) -> Environment = environment => environment,
child_style? : (ViewStyle) -> ViewStyle = style => style,
layout~ : (ViewLayoutContext) -> ViewLayoutResult,
paint? : (ViewPaintContext) -> ViewPaintPlan = _ctx => ViewPaintPlan::empty(),
event? : (ViewEventContext) -> ViewEventResult[Msg] = _ctx => {
ViewEventResult::ignored()
},
text_input_state? : (ViewPaintContext) -> TextInputState? = _ctx => None,
text_command? : (ViewEventContext, CommandIntent, String?) -> ViewTextCommandResult[
Msg,
] = (_ctx, _intent, _paste_text) => ViewTextCommandResult::ignored(),
semantics? : () -> ViewSemanticsInfo = () => ViewSemanticsInfo::new(),
focusable? : () -> Bool = () => false,
flex_weight? : () -> Double = () => 0.0,
) -> ViewNode[Msg] {
{
identity_fn: identity,
revision_fn: revision,
paint_revision_fn: paint_revision,
children_fn: children,
build_child_fn: build_child,
child_constraints_fn: child_constraints,
child_environment_fn: child_environment,
child_style_fn: child_style,
layout_fn: layout,
paint_fn: paint,
event_fn: event,
text_input_state_fn: text_input_state,
text_command_fn: text_command,
semantics_fn: semantics,
focusable_fn: focusable,
flex_weight_fn: flex_weight,
}
}
///|
fn[Msg] ViewNode::identity(self : ViewNode[Msg]) -> ViewIdentity {
(self.identity_fn)()
}
///|
fn[Msg] ViewNode::revision(self : ViewNode[Msg]) -> String {
(self.revision_fn)()
}
///|
fn[Msg] ViewNode::paint_revision(self : ViewNode[Msg]) -> String {
(self.paint_revision_fn)()
}
///|
fn[Msg] ViewNode::children(self : ViewNode[Msg]) -> Array[View[Msg]] {
(self.children_fn)()
}
///|
fn[Msg] ViewNode::build_child(
self : ViewNode[Msg],
) -> ((ComponentContext) -> View[Msg])? {
self.build_child_fn
}
///|
fn[Msg] ViewNode::child_constraints(
self : ViewNode[Msg],
constraints : Constraints,
) -> Constraints {
(self.child_constraints_fn)(constraints)
}
///|
fn[Msg] ViewNode::child_environment(
self : ViewNode[Msg],
environment : Environment,
) -> Environment {
(self.child_environment_fn)(environment)
}
///|
fn[Msg] ViewNode::child_style(
self : ViewNode[Msg],
style : ViewStyle,
) -> ViewStyle {
(self.child_style_fn)(style)
}
///|
fn[Msg] ViewNode::layout(
self : ViewNode[Msg],
context : ViewLayoutContext,
) -> ViewLayoutResult {
(self.layout_fn)(context)
}
///|
fn[Msg] ViewNode::paint(
self : ViewNode[Msg],
context : ViewPaintContext,
) -> ViewPaintPlan {
(self.paint_fn)(context)
}
///|
fn[Msg] ViewNode::event(
self : ViewNode[Msg],
context : ViewEventContext,
) -> ViewEventResult[Msg] {
(self.event_fn)(context)
}
///|
fn[Msg] ViewNode::text_input_state(
self : ViewNode[Msg],
context : ViewPaintContext,
) -> TextInputState? {
(self.text_input_state_fn)(context)
}
///|
fn[Msg] ViewNode::text_command(
self : ViewNode[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> TextCommandResult {
(self.text_command_fn)(context, intent, paste_text).result
}
///|
fn[Msg] ViewNode::text_command_result(
self : ViewNode[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> ViewTextCommandResult[Msg] {
(self.text_command_fn)(context, intent, paste_text)
}
///|
fn[Msg] ViewNode::semantics(self : ViewNode[Msg]) -> ViewSemanticsInfo {
(self.semantics_fn)()
}
///|
fn[Msg] ViewNode::focusable(self : ViewNode[Msg]) -> Bool {
(self.focusable_fn)()
}
///|
fn[Msg] ViewNode::flex_weight(self : ViewNode[Msg]) -> Double {
(self.flex_weight_fn)()
}
///|
fn[Msg, NextMsg] ViewNode::map(
self : ViewNode[Msg],
map : (Msg) -> NextMsg,
) -> ViewNode[NextMsg] {
ViewNode::new(
identity=() => self.identity(),
revision=() => self.revision(),
paint_revision=() => self.paint_revision(),
children=() => self.children().map(child => child.map(map)),
build_child=match self.build_child() {
Some(build) => Some(ctx => build(ctx).map(map))
None => None
},
child_constraints=constraints => self.child_constraints(constraints),
child_environment=environment => self.child_environment(environment),
child_style=style => self.child_style(style),
layout=context => self.layout(context),
paint=context => self.paint(context),
event=context => {
let result = self.event(context)
{
changed: result.changed,
activated: result.activated,
focused: result.focused,
captured: result.captured,
state: result.state,
text_control: result.text_control,
dirty: result.dirty,
messages: result.messages.map(message => map(message)),
}
},
text_input_state=context => self.text_input_state(context),
text_command=(context, intent, paste_text) => {
let result = self.text_command_result(context, intent, paste_text~)
{
result: result.result,
text_control: result.text_control,
messages: result.messages.map(message => map(message)),
}
},
semantics=() => self.semantics(),
focusable=() => self.focusable(),
flex_weight=() => self.flex_weight(),
)
}
///|
pub struct View[Msg] {
priv node : ViewNode[Msg]
}
///|
pub fn[Msg] View::node(
identity~ : () -> ViewIdentity,
revision? : () -> String = () => "",
paint_revision? : () -> String = () => "",
children? : () -> Array[View[Msg]] = () => [],
build_child? : ((ComponentContext) -> View[Msg])? = None,
child_constraints? : (Constraints) -> Constraints = constraints => {
constraints.loosen()
},
child_environment? : (Environment) -> Environment = environment => environment,
child_style? : (ViewStyle) -> ViewStyle = style => style,
layout~ : (ViewLayoutContext) -> ViewLayoutResult,
paint? : (ViewPaintContext) -> ViewPaintPlan = _ctx => ViewPaintPlan::empty(),
event? : (ViewEventContext) -> ViewEventResult[Msg] = _ctx => {
ViewEventResult::ignored()
},
text_input_state? : (ViewPaintContext) -> TextInputState? = _ctx => None,
text_command? : (ViewEventContext, CommandIntent, String?) -> ViewTextCommandResult[
Msg,
] = (_ctx, _intent, _paste_text) => ViewTextCommandResult::ignored(),
semantics? : () -> ViewSemanticsInfo = () => ViewSemanticsInfo::new(),
focusable? : () -> Bool = () => false,
flex_weight? : () -> Double = () => 0.0,
) -> View[Msg] {
{
node: ViewNode::new(
identity~,
revision~,
paint_revision~,
children~,
build_child~,
child_constraints~,
child_environment~,
child_style~,
layout~,
paint~,
event~,
text_input_state~,
text_command~,
semantics~,
focusable~,
flex_weight~,
),
}
}
///|
pub fn[Msg] View::identity(self : View[Msg]) -> ViewIdentity {
self.node.identity()
}
///|
pub fn[Msg] View::revision(self : View[Msg]) -> String {
self.node.revision()
}
///|
pub fn[Msg] View::paint_revision(self : View[Msg]) -> String {
self.node.paint_revision()
}
///|
pub fn[Msg] View::children(self : View[Msg]) -> Array[View[Msg]] {
self.node.children()
}
///|
pub fn[Msg] View::build_child(
self : View[Msg],
) -> ((ComponentContext) -> View[Msg])? {
self.node.build_child()
}
///|
pub fn[Msg] View::child_constraints(
self : View[Msg],
constraints : Constraints,
) -> Constraints {
self.node.child_constraints(constraints)
}
///|
pub fn[Msg] View::child_environment(
self : View[Msg],
environment : Environment,
) -> Environment {
self.node.child_environment(environment)
}
///|
pub fn[Msg] View::child_style(self : View[Msg], style : ViewStyle) -> ViewStyle {
self.node.child_style(style)
}
///|
pub fn[Msg] View::layout(
self : View[Msg],
context : ViewLayoutContext,
) -> ViewLayoutResult {
self.node.layout(context)
}
///|
pub fn[Msg] View::paint(
self : View[Msg],
context : ViewPaintContext,
) -> ViewPaintPlan {
self.node.paint(context)
}
///|
pub fn[Msg] View::event(
self : View[Msg],
context : ViewEventContext,
) -> ViewEventResult[Msg] {
self.node.event(context)
}
///|
pub fn[Msg] View::text_input_state(
self : View[Msg],
context : ViewPaintContext,
) -> TextInputState? {
self.node.text_input_state(context)
}
///|
pub fn[Msg] View::text_command(
self : View[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> TextCommandResult {
self.node.text_command(context, intent, paste_text~)
}
///|
pub fn[Msg] View::text_command_result(
self : View[Msg],
context : ViewEventContext,
intent : CommandIntent,
paste_text? : String? = None,
) -> ViewTextCommandResult[Msg] {
self.node.text_command_result(context, intent, paste_text~)
}
///|
pub fn[Msg] View::semantics(self : View[Msg]) -> ViewSemanticsInfo {
self.node.semantics()
}
///|
pub fn[Msg] View::accepts_focus(self : View[Msg]) -> Bool {
self.node.focusable()
}
///|
pub fn[Msg] View::flex_weight(self : View[Msg]) -> Double {
self.node.flex_weight()
}