///|
priv struct ElementNode {
id : ElementId
mut key : Key?
mut spec : ViewSpec
mut children : Array[ElementNode]
dirty : DirtyFlags
control : ElementControlState
component : ElementComponentState
layout_cache : ElementLayoutCache
render_cache : ElementRenderCache
}
///|
priv struct ElementTree {
root : ElementNode
}
///|
priv struct LayoutTree {
root : PlacedNode
}
///|
priv struct ElementControlState {
mut button_state : ButtonState
mut picker_expanded : Bool
mut datepicker_expanded : Bool
mut datepicker_view : DateValue
mut caret : Int
mut selection : TextRange?
mut text_selection_anchor : Int?
mut composition : String?
mut composition_cursor : TextRange?
text_undo_stack : Array[TextHistoryEntry]
text_redo_stack : Array[TextHistoryEntry]
}
///|
priv struct ElementComponentState {
mut component_child_spec : ViewSpec?
mut component_subscriptions : Array[StateSubscription]
mut component_effects : Array[ComponentEffect]
}
///|
priv struct ElementLayoutCache {
mut cached_intrinsic_size : Size?
mut cached_measure_constraints : Constraints?
mut cached_measured : MeasuredNode?
mut cached_place_frame : Rect?
mut cached_placed : PlacedNode?
}
///|
priv struct ElementRenderCache {
mut cached_render_frame : Rect?
mut cached_render_focused : ElementId?
mut cached_render : RenderNode?
}
///|
struct TextHistoryEntry {
text : String
caret : Int
selection : TextRange?
} derive(Eq, Debug, ToJson)
///|
fn TextHistoryEntry::from_element(
text : String,
element : ElementNode,
) -> TextHistoryEntry {
{ text, caret: element.control.caret, selection: element.control.selection }
}
///|
struct RenderNode {
element_id : ElementId
frame : Rect
children : Array[RenderNode]
hit_testable : Bool
paint_commands : Array[DrawCommand]
post_paint_commands : Array[DrawCommand]
} derive(Eq, Debug, ToJson)
///|
priv struct RenderTree {
root : RenderNode
}
///|
fn RenderNode::empty() -> RenderNode {
{
element_id: ElementId::new(0),
frame: Rect::new(x=0.0, y=0.0, width=0.0, height=0.0),
children: [],
hit_testable: false,
paint_commands: [],
post_paint_commands: [],
}
}
///|
fn ElementTree::new(root : ElementNode) -> ElementTree {
{ root, }
}
///|
fn ElementTree::empty() -> ElementTree {
{ root: mount_view_spec(ViewSpec::EmptySpec) }
}
///|
fn ElementTree::id(self : ElementTree) -> ElementId {
self.root.id
}
///|
fn ElementTree::measure(
self : ElementTree,
constraints : Constraints,
) -> MeasuredNode {
self.root.measure(constraints)
}
///|
fn ElementTree::find_keyed(self : ElementTree, key : String) -> ElementId? {
self.root.find_keyed(key)
}
///|
fn RenderTree::empty() -> RenderTree {
{ root: RenderNode::empty() }
}
///|
fn LayoutTree::empty() -> LayoutTree {
{
root: {
element_id: ElementId::new(0),
frame: Rect::new(x=0.0, y=0.0, width=0.0, height=0.0),
children: [],
},
}
}
///|
fn ElementControlState::new() -> ElementControlState {
{
button_state: ButtonState::Normal,
picker_expanded: false,
datepicker_expanded: false,
datepicker_view: DateValue::today(),
caret: 0,
selection: None,
text_selection_anchor: None,
composition: None,
composition_cursor: None,
text_undo_stack: [],
text_redo_stack: [],
}
}
///|
fn ElementComponentState::new(
child_spec~ : ViewSpec?,
subscriptions~ : Array[StateSubscription],
effects~ : Array[ComponentEffect],
) -> ElementComponentState {
{
component_child_spec: child_spec,
component_subscriptions: subscriptions,
component_effects: effects,
}
}
///|
fn ElementLayoutCache::empty() -> ElementLayoutCache {
{
cached_intrinsic_size: None,
cached_measure_constraints: None,
cached_measured: None,
cached_place_frame: None,
cached_placed: None,
}
}
///|
fn ElementLayoutCache::clear(self : ElementLayoutCache) -> Unit {
self.cached_intrinsic_size = None
self.cached_measure_constraints = None
self.cached_measured = None
self.cached_place_frame = None
self.cached_placed = None
}
///|
fn ElementRenderCache::empty() -> ElementRenderCache {
{
cached_render_frame: None,
cached_render_focused: None,
cached_render: None,
}
}
///|
fn ElementRenderCache::clear(self : ElementRenderCache) -> Unit {
self.cached_render_frame = None
self.cached_render_focused = None
self.cached_render = None
}
///|
struct ElementEventResult {
changed : Bool
activated : Bool
focused : ElementId?
} derive(Eq, Debug, ToJson)
///|
priv struct MountResult {
node : ElementNode
next_id : Int
}
///|
priv struct ReconcileResult {
node : ElementNode
next_id : Int
}
///|
fn mount_view_spec(spec : ViewSpec) -> ElementNode {
mount_view_spec_with_next(spec, 1).node
}
///|
fn mount_view_spec_with_next(spec : ViewSpec, next_id : Int) -> MountResult {
mount_view_spec_for_runtime(spec, None, next_id)
}
///|
fn mount_view_spec_for_runtime(
spec : ViewSpec,
runtime : RuntimeState?,
next_id : Int,
) -> MountResult {
let id = ElementId::new(next_id)
let mut next = next_id + 1
let children : Array[ElementNode] = []
let mut component_child_spec : ViewSpec? = None
let mut component_subscriptions : Array[StateSubscription] = []
let mut component_effects : Array[ComponentEffect] = []
match spec {
ComponentSpec(data) =>
match runtime {
Some(runtime) => {
let built = build_component_spec(data.component, runtime, id, [], [])
component_child_spec = Some(built.child_spec)
component_subscriptions = built.subscriptions
component_effects = built.effects
let mounted = mount_view_spec_for_runtime(
built.child_spec,
Some(runtime),
next,
)
children.push(mounted.node)
next = mounted.next_id
}
None => ()
}
FlexSpec(data) =>
for child_spec in data.children {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
PaddingSpec(data) => {
let mounted = mount_view_spec_for_runtime(data.child, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
FrameSpec(data) => {
let mounted = mount_view_spec_for_runtime(data.child, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
StackSpec(data) =>
for child_spec in data.children {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
ScrollSpec(data) => {
let mounted = mount_view_spec_for_runtime(data.child, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
GridSpec(data) =>
for child_spec in data.children {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
ListSpec(data) =>
for child_spec in data.children {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
CustomLayoutSpec(data) =>
for child_spec in data.children {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
SheetSpec(data) => {
let mounted = mount_view_spec_for_runtime(data.content, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
ContainerSpec(data) =>
match data.child {
Some(child) => {
let mounted = mount_view_spec_for_runtime(child, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
None => ()
}
ModifiedSpec(data) => {
let mounted = mount_view_spec_for_runtime(data.child, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
_ => ()
}
{
node: {
id,
key: spec.identity_key(),
spec,
children,
dirty: DirtyFlags::all(),
control: ElementControlState::new(),
component: ElementComponentState::new(
child_spec=component_child_spec,
subscriptions=component_subscriptions,
effects=component_effects,
),
layout_cache: ElementLayoutCache::empty(),
render_cache: ElementRenderCache::empty(),
},
next_id: next,
}
}
///|
fn reconcile_element(old : ElementNode, next_spec : ViewSpec) -> ElementNode {
reconcile_element_with_next(old, next_spec, 1).node
}
///|
fn reconcile_element_with_next(
old : ElementNode,
next_spec : ViewSpec,
next_id : Int,
) -> ReconcileResult {
reconcile_element_for_runtime(old, next_spec, None, next_id)
}
///|
fn reconcile_element_for_runtime(
old : ElementNode,
next_spec : ViewSpec,
runtime : RuntimeState?,
next_id : Int,
) -> ReconcileResult {
if !old.spec.same_identity(next_spec) {
old.cancel_component_tree_subscriptions()
let mounted = mount_view_spec_for_runtime(next_spec, runtime, next_id)
{ node: mounted.node, next_id: mounted.next_id }
} else {
old.spec = next_spec
old.key = old.spec.identity_key()
let children_result = if old.spec is ComponentSpec(_) {
{ children: old.children, next_id }
} else {
reconcile_children_for_runtime(old.children, old.spec, runtime, next_id)
}
old.children = children_result.children
match old.spec {
ComponentSpec(_) => {
old.dirty.mark_build()
old.clear_cached_layout_and_paint()
}
_ => {
old.dirty.mark_layout()
old.clear_cached_layout_and_paint()
}
}
{ node: old, next_id: children_result.next_id }
}
}
///|
priv struct ChildrenReconcileResult {
children : Array[ElementNode]
next_id : Int
}
///|
fn reconcile_children_for_runtime(
old_children : Array[ElementNode],
next_spec : ViewSpec,
runtime : RuntimeState?,
next_id : Int,
) -> ChildrenReconcileResult {
let next_specs = child_specs(next_spec)
let children : Array[ElementNode] = []
let used : Array[Bool] = []
for _ in old_children {
used.push(false)
}
let mut next = next_id
for index, child_spec in next_specs {
let old_index = find_reusable_child(old_children, used, child_spec, index)
match old_index {
Some(old_index) => {
used[old_index] = true
let result = reconcile_element_for_runtime(
old_children[old_index],
child_spec,
runtime,
next,
)
children.push(result.node)
next = result.next_id
}
None => {
let mounted = mount_view_spec_for_runtime(child_spec, runtime, next)
children.push(mounted.node)
next = mounted.next_id
}
}
}
for index, old_child in old_children {
if !used[index] {
old_child.cancel_component_tree_subscriptions()
}
}
{ children, next_id: next }
}
///|
fn find_reusable_child(
old_children : Array[ElementNode],
used : Array[Bool],
child_spec : ViewSpec,
index : Int,
) -> Int? {
match child_spec.identity_key() {
Some(key) => {
for old_index, old_child in old_children {
if !used[old_index] &&
old_child.key == Some(key) &&
old_child.spec.kind() == child_spec.kind() {
return Some(old_index)
}
}
None
}
None =>
if index < old_children.length() &&
!used[index] &&
old_children[index].key is None &&
old_children[index].spec.kind() == child_spec.kind() {
Some(index)
} else {
None
}
}
}
///|
fn child_specs(spec : ViewSpec) -> Array[ViewSpec] {
match spec {
FlexSpec(data) => data.children
PaddingSpec(data) => [data.child]
FrameSpec(data) => [data.child]
StackSpec(data) => data.children
ScrollSpec(data) => [data.child]
GridSpec(data) => data.children
ListSpec(data) => data.children
CustomLayoutSpec(data) => data.children
SheetSpec(data) => [data.content]
ContainerSpec(data) =>
match data.child {
Some(child) => [child]
None => []
}
ModifiedSpec(data) => [data.child]
ComponentSpec(_) => []
_ => []
}
}
///|
fn ElementNode::rebuild_dirty_components(
self : ElementNode,
runtime : RuntimeState,
next_id : Int,
) -> ReconcileResult {
let mut next = next_id
if self.dirty.needs_build {
match self.spec {
ComponentSpec(data) => {
let built = build_component_spec(
data.component,
runtime,
self.id,
self.component.component_subscriptions,
self.component.component_effects,
)
self.component.component_subscriptions = built.subscriptions
self.component.component_effects = built.effects
self.component.component_child_spec = Some(built.child_spec)
let result = if self.children.length() > 0 {
reconcile_element_for_runtime(
self.children[0],
built.child_spec,
Some(runtime),
next,
)
} else {
let mounted = mount_view_spec_for_runtime(
built.child_spec,
Some(runtime),
next,
)
{ node: mounted.node, next_id: mounted.next_id }
}
self.children = [result.node]
next = result.next_id
}
_ => ()
}
self.dirty.needs_build = false
self.dirty.mark_layout()
self.clear_cached_layout_and_paint()
}
let children : Array[ElementNode] = []
for child in self.children {
let result = child.rebuild_dirty_components(runtime, next)
children.push(result.node)
next = result.next_id
}
self.children = children
{ node: self, next_id: next }
}
///|
fn ElementNode::has_dirty_build(self : ElementNode) -> Bool {
if self.dirty.needs_build {
true
} else {
self.children.any(child => child.has_dirty_build())
}
}
///|
fn ElementNode::has_dirty_layout(self : ElementNode) -> Bool {
if self.dirty.needs_layout {
true
} else {
self.children.any(child => child.has_dirty_layout())
}
}
///|
fn ElementNode::has_dirty_paint(self : ElementNode) -> Bool {
if self.dirty.needs_paint {
true
} else {
self.children.any(child => child.has_dirty_paint())
}
}
///|
fn ElementNode::mark_build_dirty_by_id(
self : ElementNode,
id : ElementId,
) -> Bool {
if self.id == id {
self.dirty.mark_build()
self.clear_cached_layout_and_paint()
true
} else {
let mut found = false
for child in self.children {
found = child.mark_build_dirty_by_id(id) || found
}
if found {
self.dirty.mark_layout()
self.clear_cached_layout_and_paint()
}
found
}
}
///|
fn ElementNode::mark_paint_dirty_by_id(
self : ElementNode,
id : ElementId,
) -> Bool {
if self.id == id {
self.dirty.mark_paint()
self.render_cache.clear()
true
} else {
let mut found = false
for child in self.children {
found = child.mark_paint_dirty_by_id(id) || found
}
if found {
self.dirty.mark_paint()
self.render_cache.clear()
}
found
}
}
///|
fn ElementNode::mark_root_component_build_dirty(self : ElementNode) -> Bool {
match self.spec {
ComponentSpec(_) => {
self.dirty.mark_build()
self.clear_cached_layout_and_paint()
true
}
_ => false
}
}
///|
fn ElementNode::mark_component_tree_build_dirty(self : ElementNode) -> Bool {
let mut found = false
match self.spec {
ComponentSpec(_) => {
self.dirty.mark_build()
self.clear_cached_layout_and_paint()
found = true
}
_ => ()
}
for child in self.children {
found = child.mark_component_tree_build_dirty() || found
}
if found {
self.dirty.mark_layout()
self.clear_cached_layout_and_paint()
}
found
}
///|
fn ElementNode::mark_subtree_layout_dirty(self : ElementNode) -> Unit {
self.dirty.mark_layout()
self.clear_cached_layout_and_paint()
for child in self.children {
child.mark_subtree_layout_dirty()
}
}
///|
fn ElementNode::cancel_component_tree_subscriptions(
self : ElementNode,
reason? : ComponentEffectCancelReason = ComponentEffectCancelReason::EffectUnmounted,
) -> Unit {
self.cancel_component_subscriptions()
self.cancel_component_effects(reason~)
for child in self.children {
child.cancel_component_tree_subscriptions(reason~)
}
}
///|
fn ElementNode::cancel_component_subscriptions(self : ElementNode) -> Unit {
for subscription in self.component.component_subscriptions {
subscription.cancel()
}
self.component.component_subscriptions.clear()
}
///|
fn ElementNode::cancel_component_effects(
self : ElementNode,
reason~ : ComponentEffectCancelReason,
) -> Unit {
cancel_component_effects(self.component.component_effects, reason~)
self.component.component_effects.clear()
}
///|
fn ElementNode::clear_cached_layout_and_paint(self : ElementNode) -> Unit {
self.layout_cache.clear()
self.render_cache.clear()
}