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 node: layout_core::NodeId,
28}
29
30impl Child {
31 pub(crate) fn node(&self) -> layout_core::NodeId {
32 self.node
33 }
34}
35
36pub(crate) fn make_child(widget: Box<dyn LayoutItem>) -> Child {
40 let node = widget.layout_node();
41 let rect = track_layout(node);
42 let item = Rc::new(RefCell::new(widget));
43 let segment = mount_item_segment(Rc::clone(&item));
44 Child {
45 item,
46 rect,
47 segment,
48 node,
49 }
50}
51
52pub(crate) type TrackedChildren = Vec<Child>;
53
54pub(crate) fn mount_item_segment(item: Rc<RefCell<Box<dyn LayoutItem>>>) -> Rc<Segment> {
58 let name = item
59 .try_borrow()
60 .map(|i| i.debug_name())
61 .unwrap_or("Component");
62 Segment::mount_fn_named(name, move || item.try_borrow().ok().map(|i| i.view()))
63}
64
65pub(crate) trait LeafWidget {
66 fn layout_leaf(&self) -> &LayoutLeaf;
67}
68
69pub trait LayoutItem: Component {
70 fn layout_node(&self) -> NodeId;
71
72 fn pointer_opaque(&self) -> bool {
81 true
82 }
83}
84
85pub struct ClippedItem {
96 inner: Box<dyn LayoutItem>,
97 rect: RwSignal<Rect>,
98 axis: ClipAxis,
99}
100
101#[derive(Clone, Copy, PartialEq, Eq, Debug)]
103pub enum ClipAxis {
104 Both,
106 Horizontal,
108 Vertical,
110}
111
112const UNBOUNDED: f32 = 1.0e6;
116
117impl ClippedItem {
118 pub fn new(inner: Box<dyn LayoutItem>) -> Self {
119 Self::along(inner, ClipAxis::Both)
120 }
121
122 pub fn along(inner: Box<dyn LayoutItem>, axis: ClipAxis) -> Self {
129 let rect = track_layout(inner.layout_node()).expect("clipped item's node not registered");
130 Self { inner, rect, axis }
131 }
132
133 fn clip(&self) -> Rect {
134 let rect = self.rect.get();
135 match self.axis {
136 ClipAxis::Both => rect,
137 ClipAxis::Horizontal => Rect::new(rect.x, -UNBOUNDED, rect.width, UNBOUNDED * 2.0),
138 ClipAxis::Vertical => Rect::new(-UNBOUNDED, rect.y, UNBOUNDED * 2.0, rect.height),
139 }
140 }
141}
142
143impl LayoutItem for ClippedItem {
144 fn layout_node(&self) -> NodeId {
145 self.inner.layout_node()
146 }
147
148 fn pointer_opaque(&self) -> bool {
149 self.inner.pointer_opaque()
150 }
151}
152
153impl Component for ClippedItem {
154 fn view(&self) -> RenderNode {
155 RenderNode::Clip {
156 rect: self.clip(),
157 radius: renderer_core::BorderRadius::zero(),
158 children: ui_tree::NodeVec::collect([self.inner.view()]),
159 }
160 }
161
162 fn on_event(&mut self, event: &Event) -> EventResult {
163 let outside = |x: f64, y: f64| !self.clip().contains(x as f32, y as f32);
164 match event {
165 Event::PointerPressed { x, y, .. } | Event::PointerMoved { x, y, .. }
166 if outside(*x, *y) =>
167 {
168 EventResult::Ignored
169 }
170 _ => self.inner.on_event(event),
171 }
172 }
173
174 fn debug_name(&self) -> &'static str {
175 "Clipped"
176 }
177}
178
179pub struct Holding {
193 inner: Box<dyn LayoutItem>,
194 _effects: Vec<reactive_core::Effect>,
195}
196
197impl Holding {
198 pub fn new(inner: Box<dyn LayoutItem>, effects: Vec<reactive_core::Effect>) -> Self {
199 Self {
200 inner,
201 _effects: effects,
202 }
203 }
204}
205
206impl LayoutItem for Holding {
207 fn layout_node(&self) -> NodeId {
208 self.inner.layout_node()
209 }
210
211 fn pointer_opaque(&self) -> bool {
212 self.inner.pointer_opaque()
213 }
214}
215
216impl Component for Holding {
217 fn view(&self) -> RenderNode {
218 self.inner.view()
219 }
220
221 fn on_event(&mut self, event: &Event) -> EventResult {
222 self.inner.on_event(event)
223 }
224
225 fn debug_name(&self) -> &'static str {
226 self.inner.debug_name()
227 }
228}
229
230impl<T: LeafWidget + Component> LayoutItem for T {
231 fn layout_node(&self) -> NodeId {
232 self.layout_leaf().node
233 }
234}
235
236impl Component for Box<dyn LayoutItem> {
238 fn view(&self) -> RenderNode {
239 (**self).view()
240 }
241
242 fn on_event(&mut self, event: &Event) -> EventResult {
243 (**self).on_event(event)
244 }
245
246 fn debug_name(&self) -> &'static str {
247 (**self).debug_name()
248 }
249}
250
251impl LayoutItem for Box<dyn LayoutItem> {
252 fn layout_node(&self) -> NodeId {
253 (**self).layout_node()
254 }
255
256 fn pointer_opaque(&self) -> bool {
257 (**self).pointer_opaque()
258 }
259}
260
261pub fn box_item(item: impl LayoutItem + 'static) -> Box<dyn LayoutItem> {
263 Box::new(item)
264}
265
266pub(crate) fn register_container(
267 layout_style: LayoutStyle,
268 children: Vec<Box<dyn LayoutItem>>,
269) -> Result<(NodeId, RwSignal<Rect>, TrackedChildren), LayoutError> {
270 let child_nodes = children.iter().map(|c| c.layout_node()).collect::<Vec<_>>();
271 let node = new_container(layout_style, &child_nodes)?;
272 let rect = track_layout(node).expect("new_container always registers a signal");
273 let children = children.into_iter().map(make_child).collect();
274 Ok((node, rect, children))
275}
276
277#[macro_export]
279macro_rules! impl_leaf_widget {
280 ($struct:ident) => {
281 impl $crate::layout_item::LeafWidget for $struct {
282 fn layout_leaf(&self) -> &$crate::layout_leaf::LayoutLeaf {
283 &self.leaf
284 }
285 }
286 };
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292 use crate::StyledContainer;
293 use crate::context::{compute_layout, reset_layout_runtime};
294 use layout_core::AvailableSpace;
295 use platform_core::{PointerButton, PointerSource};
296 use renderer_core::RectStyle;
297 use std::cell::Cell;
298 use std::rc::Rc;
299
300 fn press(x: f64, y: f64) -> Event {
301 Event::PointerPressed {
302 x,
303 y,
304 button: PointerButton::Primary,
305 source: PointerSource::Mouse,
306 }
307 }
308
309 fn release(x: f64, y: f64) -> Event {
310 Event::PointerReleased {
311 x,
312 y,
313 button: PointerButton::Primary,
314 source: PointerSource::Mouse,
315 }
316 }
317
318 #[test]
324 fn a_press_outside_the_clip_never_reaches_what_it_hides() {
325 let pressed = Rc::new(Cell::new(false));
326 let sink = Rc::clone(&pressed);
327 reset_layout_runtime();
328 let inner = StyledContainer::new(
329 LayoutStyle::new().flex_row().width(100.0).height(20.0),
330 |_r| RectStyle::default(),
331 vec![],
332 )
333 .unwrap()
334 .on_press(move || sink.set(true));
335 let mut clipped = ClippedItem::new(Box::new(
337 StyledContainer::new(
338 LayoutStyle::new().flex_row().width(40.0).height(20.0),
339 |_r| RectStyle::default(),
340 vec![Box::new(inner)],
341 )
342 .unwrap(),
343 ));
344 compute_layout(
345 clipped.layout_node(),
346 AvailableSpace::Definite(40.0),
347 AvailableSpace::Definite(20.0),
348 )
349 .unwrap();
350
351 clipped.on_event(&press(80.0, 10.0));
352 clipped.on_event(&release(80.0, 10.0));
353 assert!(
354 !pressed.get(),
355 "a tap 40px past the clip's edge reached the item hidden behind it"
356 );
357
358 clipped.on_event(&press(10.0, 10.0));
359 clipped.on_event(&release(10.0, 10.0));
360 assert!(
361 pressed.get(),
362 "and a tap on the part that is actually drawn still has to land"
363 );
364 }
365}