Skip to main content

telar_ui_core/
layout_item.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use geometry_core::Rect;
5use layout_core::{LayoutError, LayoutStyle, NodeId};
6use platform_core::Event;
7use reactive_core::RwSignal;
8use ui_tree::{Component, EventResult, RenderNode, Segment};
9
10use crate::context::{new_container, track_layout};
11use crate::layout_leaf::LayoutLeaf;
12
13/// A container child. The boxed widget is shared (`Rc<RefCell<…>>`) between event dispatch (which
14/// borrows it mutably) and its render `segment` (which borrows it immutably to flatten its `view()`)
15/// — they never overlap because dispatch is batched. `rect` is the child's layout signal for hit-testing.
16/// `Clone` is a cheap handle copy (all fields are `Rc`/signal): a reactive list clones a `Child` to move
17/// a reused item to its new position without rebuilding it.
18#[derive(Clone)]
19pub(crate) struct Child {
20    pub(crate) item: Rc<RefCell<Box<dyn LayoutItem>>>,
21    pub(crate) rect: Option<RwSignal<Rect>>,
22    pub(crate) segment: Rc<Segment>,
23}
24
25impl Child {
26    /// The child's layout node, read through the shared widget.
27    pub(crate) fn node(&self) -> layout_core::NodeId {
28        self.item.borrow().layout_node()
29    }
30}
31
32/// Registers an already-built widget as a container child: tracks its layout rect and mounts its render
33/// segment. Used by reactive lists to fold a freshly-built item into the child set (the per-item half of
34/// [`register_container`]).
35pub(crate) fn make_child(widget: Box<dyn LayoutItem>) -> Child {
36    let rect = track_layout(widget.layout_node());
37    let item = Rc::new(RefCell::new(widget));
38    let segment = mount_item_segment(Rc::clone(&item));
39    Child {
40        item,
41        rect,
42        segment,
43    }
44}
45
46pub(crate) type TrackedChildren = Vec<Child>;
47
48/// Mounts a reactive segment that renders a shared boxed item via its `view()`. Uses `try_borrow`
49/// so a re-entrant render while the item is mid event-dispatch (mutably borrowed) keeps the previous
50/// frame instead of panicking; a later flush re-runs it.
51pub(crate) fn mount_item_segment(item: Rc<RefCell<Box<dyn LayoutItem>>>) -> Rc<Segment> {
52    let name = item
53        .try_borrow()
54        .map(|i| i.debug_name())
55        .unwrap_or("Component");
56    Segment::mount_fn_named(name, move || item.try_borrow().ok().map(|i| i.view()))
57}
58
59pub(crate) trait LeafWidget {
60    fn layout_leaf(&self) -> &LayoutLeaf;
61}
62
63pub trait LayoutItem: Component {
64    fn layout_node(&self) -> NodeId;
65}
66
67/// Wraps a child so its rendered output is clipped to the child's own layout rect. When the child
68/// collapses to a zero rect (e.g. a section hidden via `display:none`), the clip is empty, so nothing
69/// inside draws — even a widget left with a stale rect or one that paints at fixed coordinates. Layout
70/// is unchanged: `layout_node` passes through to the wrapped child.
71pub struct ClippedItem {
72    inner: Box<dyn LayoutItem>,
73    rect: RwSignal<Rect>,
74}
75
76impl ClippedItem {
77    pub fn new(inner: Box<dyn LayoutItem>) -> Self {
78        let rect = track_layout(inner.layout_node()).expect("clipped item's node not registered");
79        Self { inner, rect }
80    }
81}
82
83impl LayoutItem for ClippedItem {
84    fn layout_node(&self) -> NodeId {
85        self.inner.layout_node()
86    }
87}
88
89impl Component for ClippedItem {
90    fn view(&self) -> RenderNode {
91        RenderNode::Clip {
92            rect: self.rect.get(),
93            radius: renderer_core::BorderRadius::zero(),
94            children: ui_tree::NodeVec::collect([self.inner.view()]),
95        }
96    }
97
98    fn on_event(&mut self, event: &Event) -> EventResult {
99        self.inner.on_event(event)
100    }
101
102    fn debug_name(&self) -> &'static str {
103        "Clipped"
104    }
105}
106
107impl<T: LeafWidget + Component> LayoutItem for T {
108    fn layout_node(&self) -> NodeId {
109        self.layout_leaf().node
110    }
111}
112
113// Lets an already-boxed child (e.g. the `Box<dyn LayoutItem>` returned by a transpiled `.rsx` component) pass back through `box_item`/`children!` without a second manual wrap, so components compose as `[view]` children.
114impl Component for Box<dyn LayoutItem> {
115    fn view(&self) -> RenderNode {
116        (**self).view()
117    }
118
119    fn on_event(&mut self, event: &Event) -> EventResult {
120        (**self).on_event(event)
121    }
122
123    fn debug_name(&self) -> &'static str {
124        (**self).debug_name()
125    }
126}
127
128impl LayoutItem for Box<dyn LayoutItem> {
129    fn layout_node(&self) -> NodeId {
130        (**self).layout_node()
131    }
132}
133
134// pub so the `children!` macro can call it from any crate without naming the module
135pub fn box_item(item: impl LayoutItem + 'static) -> Box<dyn LayoutItem> {
136    Box::new(item)
137}
138
139pub(crate) fn register_container(
140    layout_style: LayoutStyle,
141    children: Vec<Box<dyn LayoutItem>>,
142) -> Result<(NodeId, RwSignal<Rect>, TrackedChildren), LayoutError> {
143    let child_nodes = children.iter().map(|c| c.layout_node()).collect::<Vec<_>>();
144    let node = new_container(layout_style, &child_nodes)?;
145    let rect = track_layout(node).expect("new_container always registers a signal");
146    let children = children.into_iter().map(make_child).collect();
147    Ok((node, rect, children))
148}
149
150/// Implements `LeafWidget` for a struct that has a `leaf: LayoutLeaf` field.
151#[macro_export]
152macro_rules! impl_leaf_widget {
153    ($struct:ident) => {
154        impl $crate::layout_item::LeafWidget for $struct {
155            fn layout_leaf(&self) -> &$crate::layout_leaf::LayoutLeaf {
156                &self.leaf
157            }
158        }
159    };
160}