use std::cell::RefCell;
use std::rc::Rc;
use crate::base::{Rect, Size};
use crate::reactive::{request_frame, untrack, Scope};
use super::tree::{Inst, InstPayload, TreeCore, ViewId};
use super::view::{View, ViewNode};
pub(super) fn mount_view(
core: &Rc<RefCell<TreeCore>>,
cx: Scope,
view: View,
parent: Option<ViewId>,
) -> ViewId {
match view.0 {
ViewNode::Element(mut el) => {
let (id, layout) = {
let mut c = core.borrow_mut();
let mut style = el.style.clone();
if let Some(floor) = el.padding_floor {
apply_padding_floor(&mut style, floor);
}
let layout = c.layout.add(style);
let id = ViewId(c.insts.insert(Inst {
parent,
children: Vec::new(),
layout,
focusable: el.focusable,
focus_trap: el.focus_trap,
focus_memory: el.focus_memory,
access: el.access.clone(),
payload: InstPayload::Element {
draw: el.draw.map(|d| Rc::new(RefCell::new(d))),
handlers: Rc::new(RefCell::new(el.handlers)),
shortcuts: Rc::new(RefCell::new(el.shortcuts)),
},
}));
if el.autofocus {
c.pending_autofocus = Some(id);
}
attach(&mut c, parent, id);
(id, layout)
};
if let Some(mut style_fn) = el.style_fn.take() {
let core_for_style = core.clone();
let floor = el.padding_floor;
cx.effect(move || {
let mut style = style_fn(); if let Some(f) = floor {
apply_padding_floor(&mut style, f);
}
let mut c = core_for_style.borrow_mut();
if !c.layout.is_alive(layout) {
return;
}
c.layout.set_style(layout, style);
let anchor = resolve_anchor(&c.layout, layout);
c.dirty_subtrees.push(anchor);
drop(c);
request_frame();
});
}
for child in el.children {
mount_view(core, cx, child, Some(id));
}
id
}
ViewNode::Text(t) => {
let mut c = core.borrow_mut();
let content = t.content;
let measured = content.clone();
let layout = c.layout.add_leaf(
t.style,
Box::new(move |avail: Size| crate::text::measure(&measured, avail)),
);
let id = ViewId(c.insts.insert(Inst {
parent,
children: Vec::new(),
layout,
focusable: false,
focus_trap: false,
focus_memory: false,
access: Default::default(),
payload: InstPayload::Text { content },
}));
attach(&mut c, parent, id);
id
}
ViewNode::Dyn(d) => {
let dyn_id = {
let mut c = core.borrow_mut();
let layout = c.layout.add(d.style.clone());
let id = ViewId(c.insts.insert(Inst {
parent,
children: Vec::new(),
layout,
focusable: false,
focus_trap: false,
focus_memory: false,
access: Default::default(),
payload: InstPayload::Dyn,
}));
attach(&mut c, parent, id);
id
};
let mut build = d.build;
let core_for_effect = core.clone();
let holder: Rc<RefCell<Option<Scope>>> = Rc::new(RefCell::new(None));
cx.effect(move || {
if let Some(prev) = holder.borrow_mut().take() {
prev.dispose();
}
let child_cx = cx.child();
let view = build(child_cx);
*holder.borrow_mut() = Some(child_cx);
let core2 = core_for_effect.clone();
let mounted = untrack(|| mount_view(&core2, child_cx, view, Some(dyn_id)));
let core3 = core_for_effect.clone();
child_cx.on_cleanup(move || remove_subtree(&core3, mounted));
{
let mut c = core_for_effect.borrow_mut();
let rect = c
.insts
.get(dyn_id.0)
.map(|inst| c.layout.rect(inst.layout))
.unwrap_or(Rect::ZERO);
if rect.is_empty() {
c.damage_all();
} else {
c.damage_rect(rect);
}
c.needs_layout = true;
}
request_frame();
});
dyn_id
}
}
}
fn apply_padding_floor(style: &mut crate::layout::Style, floor: crate::layout::Edges) {
style.padding.left = style.padding.left.max(floor.left);
style.padding.right = style.padding.right.max(floor.right);
style.padding.top = style.padding.top.max(floor.top);
style.padding.bottom = style.padding.bottom.max(floor.bottom);
}
fn resolve_anchor(
layout: &crate::layout::LayoutTree,
node: crate::layout::LayoutId,
) -> crate::layout::LayoutId {
use crate::layout::Dimension;
let content_sized = |id: crate::layout::LayoutId| {
layout
.style(id)
.map(|s| matches!(s.width, Dimension::Auto) || matches!(s.height, Dimension::Auto))
.unwrap_or(true)
};
let Some(mut cur) = layout.parent(node) else {
return node;
};
while content_sized(cur) {
match layout.parent(cur) {
Some(p) => cur = p,
None => break,
}
}
cur
}
fn attach(core: &mut TreeCore, parent: Option<ViewId>, child: ViewId) {
if let Some(p) = parent {
if let Some(pinst) = core.insts.get_mut(p.0) {
pinst.children.push(child);
}
if let (Some(pl), Some(cl)) = (
core.insts.get(p.0).map(|i| i.layout),
core.insts.get(child.0).map(|i| i.layout),
) {
core.layout.add_child(pl, cl);
}
}
}
pub(super) fn remove_subtree(core: &Rc<RefCell<TreeCore>>, root: ViewId) {
let mut c = core.borrow_mut();
let Some(root_inst) = c.insts.get(root.0) else {
return;
};
let root_layout = root_inst.layout;
let parent = root_inst.parent;
let root_rect = c.layout.rect(root_layout);
if let Some(p) = parent {
if let Some(pinst) = c.insts.get_mut(p.0) {
pinst.children.retain(|k| *k != root);
}
}
c.layout.remove(root_layout);
let mut stack = vec![root];
while let Some(id) = stack.pop() {
if let Some(inst) = c.insts.remove(id.0) {
stack.extend(inst.children);
if c.focus == Some(id) {
c.focus = None;
}
}
}
c.damage_rect(root_rect);
c.needs_layout = true;
}