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#[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 pub(crate) fn node(&self) -> layout_core::NodeId {
28 self.item.borrow().layout_node()
29 }
30}
31
32pub(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
48pub(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 fn pointer_opaque(&self) -> bool {
75 true
76 }
77}
78
79pub struct ClippedItem {
90 inner: Box<dyn LayoutItem>,
91 rect: RwSignal<Rect>,
92 axis: ClipAxis,
93}
94
95#[derive(Clone, Copy, PartialEq, Eq, Debug)]
97pub enum ClipAxis {
98 Both,
100 Horizontal,
102 Vertical,
104}
105
106const UNBOUNDED: f32 = 1.0e6;
110
111impl ClippedItem {
112 pub fn new(inner: Box<dyn LayoutItem>) -> Self {
113 Self::along(inner, ClipAxis::Both)
114 }
115
116 pub fn along(inner: Box<dyn LayoutItem>, axis: ClipAxis) -> Self {
123 let rect = track_layout(inner.layout_node()).expect("clipped item's node not registered");
124 Self { inner, rect, axis }
125 }
126
127 fn clip(&self) -> Rect {
128 let rect = self.rect.get();
129 match self.axis {
130 ClipAxis::Both => rect,
131 ClipAxis::Horizontal => Rect::new(rect.x, -UNBOUNDED, rect.width, UNBOUNDED * 2.0),
132 ClipAxis::Vertical => Rect::new(-UNBOUNDED, rect.y, UNBOUNDED * 2.0, rect.height),
133 }
134 }
135}
136
137impl LayoutItem for ClippedItem {
138 fn layout_node(&self) -> NodeId {
139 self.inner.layout_node()
140 }
141
142 fn pointer_opaque(&self) -> bool {
143 self.inner.pointer_opaque()
144 }
145}
146
147impl Component for ClippedItem {
148 fn view(&self) -> RenderNode {
149 RenderNode::Clip {
150 rect: self.clip(),
151 radius: renderer_core::BorderRadius::zero(),
152 children: ui_tree::NodeVec::collect([self.inner.view()]),
153 }
154 }
155
156 fn on_event(&mut self, event: &Event) -> EventResult {
157 let outside = |x: f64, y: f64| !self.clip().contains(x as f32, y as f32);
158 match event {
159 Event::PointerPressed { x, y, .. } | Event::PointerMoved { x, y, .. }
160 if outside(*x, *y) =>
161 {
162 EventResult::Ignored
163 }
164 _ => self.inner.on_event(event),
165 }
166 }
167
168 fn debug_name(&self) -> &'static str {
169 "Clipped"
170 }
171}
172
173pub struct Holding {
187 inner: Box<dyn LayoutItem>,
188 _effects: Vec<reactive_core::Effect>,
189}
190
191impl Holding {
192 pub fn new(inner: Box<dyn LayoutItem>, effects: Vec<reactive_core::Effect>) -> Self {
193 Self {
194 inner,
195 _effects: effects,
196 }
197 }
198}
199
200impl LayoutItem for Holding {
201 fn layout_node(&self) -> NodeId {
202 self.inner.layout_node()
203 }
204
205 fn pointer_opaque(&self) -> bool {
206 self.inner.pointer_opaque()
207 }
208}
209
210impl Component for Holding {
211 fn view(&self) -> RenderNode {
212 self.inner.view()
213 }
214
215 fn on_event(&mut self, event: &Event) -> EventResult {
216 self.inner.on_event(event)
217 }
218
219 fn debug_name(&self) -> &'static str {
220 self.inner.debug_name()
221 }
222}
223
224impl<T: LeafWidget + Component> LayoutItem for T {
225 fn layout_node(&self) -> NodeId {
226 self.layout_leaf().node
227 }
228}
229
230impl Component for Box<dyn LayoutItem> {
232 fn view(&self) -> RenderNode {
233 (**self).view()
234 }
235
236 fn on_event(&mut self, event: &Event) -> EventResult {
237 (**self).on_event(event)
238 }
239
240 fn debug_name(&self) -> &'static str {
241 (**self).debug_name()
242 }
243}
244
245impl LayoutItem for Box<dyn LayoutItem> {
246 fn layout_node(&self) -> NodeId {
247 (**self).layout_node()
248 }
249
250 fn pointer_opaque(&self) -> bool {
251 (**self).pointer_opaque()
252 }
253}
254
255pub fn box_item(item: impl LayoutItem + 'static) -> Box<dyn LayoutItem> {
257 Box::new(item)
258}
259
260pub(crate) fn register_container(
261 layout_style: LayoutStyle,
262 children: Vec<Box<dyn LayoutItem>>,
263) -> Result<(NodeId, RwSignal<Rect>, TrackedChildren), LayoutError> {
264 let child_nodes = children.iter().map(|c| c.layout_node()).collect::<Vec<_>>();
265 let node = new_container(layout_style, &child_nodes)?;
266 let rect = track_layout(node).expect("new_container always registers a signal");
267 let children = children.into_iter().map(make_child).collect();
268 Ok((node, rect, children))
269}
270
271#[macro_export]
273macro_rules! impl_leaf_widget {
274 ($struct:ident) => {
275 impl $crate::layout_item::LeafWidget for $struct {
276 fn layout_leaf(&self) -> &$crate::layout_leaf::LayoutLeaf {
277 &self.leaf
278 }
279 }
280 };
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 use crate::StyledContainer;
287 use crate::context::{compute_layout, reset_layout_runtime};
288 use layout_core::AvailableSpace;
289 use platform_core::{PointerButton, PointerSource};
290 use renderer_core::RectStyle;
291 use std::cell::Cell;
292 use std::rc::Rc;
293
294 fn press(x: f64, y: f64) -> Event {
295 Event::PointerPressed {
296 x,
297 y,
298 button: PointerButton::Primary,
299 source: PointerSource::Mouse,
300 }
301 }
302
303 fn release(x: f64, y: f64) -> Event {
304 Event::PointerReleased {
305 x,
306 y,
307 button: PointerButton::Primary,
308 source: PointerSource::Mouse,
309 }
310 }
311
312 #[test]
318 fn a_press_outside_the_clip_never_reaches_what_it_hides() {
319 let pressed = Rc::new(Cell::new(false));
320 let sink = Rc::clone(&pressed);
321 reset_layout_runtime();
322 let inner = StyledContainer::new(
323 LayoutStyle::new().flex_row().width(100.0).height(20.0),
324 |_r| RectStyle::default(),
325 vec![],
326 )
327 .unwrap()
328 .on_press(move || sink.set(true));
329 let mut clipped = ClippedItem::new(Box::new(
331 StyledContainer::new(
332 LayoutStyle::new().flex_row().width(40.0).height(20.0),
333 |_r| RectStyle::default(),
334 vec![Box::new(inner)],
335 )
336 .unwrap(),
337 ));
338 compute_layout(
339 clipped.layout_node(),
340 AvailableSpace::Definite(40.0),
341 AvailableSpace::Definite(20.0),
342 )
343 .unwrap();
344
345 clipped.on_event(&press(80.0, 10.0));
346 clipped.on_event(&release(80.0, 10.0));
347 assert!(
348 !pressed.get(),
349 "a tap 40px past the clip's edge reached the item hidden behind it"
350 );
351
352 clipped.on_event(&press(10.0, 10.0));
353 clipped.on_event(&release(10.0, 10.0));
354 assert!(
355 pressed.get(),
356 "and a tap on the part that is actually drawn still has to land"
357 );
358 }
359}