Skip to main content

sequoia_view/
leaf.rs

1use sequoia_core::Size;
2use sequoia_input::{CursorIcon, Event};
3use sequoia_layout::{LayoutCtx, LayoutStyle};
4use sequoia_style::VisualState;
5
6use crate::view::{EventCtx, PaintCtx};
7
8pub trait Leaf: 'static {
9    fn layout_style(&self) -> LayoutStyle {
10        LayoutStyle::default()
11    }
12
13    fn intrinsic_size(&self, _ctx: &mut LayoutCtx) -> Option<Size> {
14        None
15    }
16
17    fn paint(&self, ctx: &mut PaintCtx);
18
19    fn on_event(&self, _event: &Event, _ctx: &mut EventCtx) {}
20
21    fn focusable(&self) -> bool {
22        false
23    }
24
25    fn has_visual_state(&self) -> bool {
26        false
27    }
28
29    fn cursor(&self, _state: VisualState) -> CursorIcon {
30        CursorIcon::Default
31    }
32
33    fn track(&self) {}
34}
35
36#[macro_export]
37macro_rules! impl_view_for_leaf {
38    ($t:ty) => {
39        impl $crate::View for $t {
40            fn body(&self) -> impl $crate::View + 'static {
41                <$t as $crate::Leaf>::track(self);
42                $crate::Unit
43            }
44            fn is_leaf(&self) -> bool {
45                true
46            }
47            fn layout_style(&self) -> $crate::LayoutStyle {
48                <$t as $crate::Leaf>::layout_style(self)
49            }
50            fn intrinsic_size(&self, ctx: &mut $crate::LayoutCtx) -> Option<$crate::Size> {
51                <$t as $crate::Leaf>::intrinsic_size(self, ctx)
52            }
53            fn paint_self(&self, ctx: &mut $crate::PaintCtx) {
54                <$t as $crate::Leaf>::paint(self, ctx)
55            }
56            fn on_event(&self, event: &$crate::Event, ctx: &mut $crate::EventCtx) {
57                <$t as $crate::Leaf>::on_event(self, event, ctx)
58            }
59            fn focusable(&self) -> bool {
60                <$t as $crate::Leaf>::focusable(self)
61            }
62            fn has_visual_state(&self) -> bool {
63                <$t as $crate::Leaf>::has_visual_state(self)
64            }
65            fn cursor_icon(&self, state: $crate::VisualState) -> $crate::CursorIcon {
66                <$t as $crate::Leaf>::cursor(self, state)
67            }
68        }
69    };
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::View;
76    use crate::views::{Row, Unit};
77    use sequoia_core::{Color, Length, Point};
78
79    #[derive(Clone)]
80    struct Dot {
81        color: Color,
82    }
83
84    impl Leaf for Dot {
85        fn layout_style(&self) -> LayoutStyle {
86            LayoutStyle::default()
87                .width(Length::Px(12.0))
88                .height(Length::Px(12.0))
89        }
90
91        fn paint(&self, ctx: &mut PaintCtx) {
92            let c = Point::new(ctx.rect.origin.x + 6.0, ctx.rect.origin.y + 6.0);
93            ctx.disk(c, 6.0, self.color);
94        }
95    }
96
97    impl_view_for_leaf!(Dot);
98
99    #[test]
100    fn leaf_is_a_leaf_view() {
101        let d = Dot { color: Color::RED };
102        assert!(View::is_leaf(&d));
103        let _ = View::body(&d);
104    }
105
106    #[test]
107    fn leaf_paints_disk_without_panicking() {
108        use crate::runtime::Runtime;
109        use sequoia_core::Rect;
110        use sequoia_text::TextSystem;
111
112        let rt = Runtime::new();
113        rt.mount(Row::new().child(Dot {
114            color: Color::GREEN,
115        }));
116        let mut text = TextSystem::new();
117        rt.layout(Rect::from_xywh(0.0, 0.0, 200.0, 100.0), &mut text);
118        let mut list = sequoia_core::RenderList::new();
119        rt.paint(&mut list, &mut text);
120        assert!(!list.is_empty(), "leaf should paint something");
121        assert!(rt.errors().is_empty());
122    }
123
124    #[test]
125    fn leaf_with_signal_tracks() {
126        use sequoia_reactive::Signal;
127
128        #[derive(Clone)]
129        struct Badge {
130            label: Signal<String>,
131        }
132
133        impl Leaf for Badge {
134            fn track(&self) {
135                let _ = self.label.get();
136            }
137
138            fn paint(&self, ctx: &mut PaintCtx) {
139                let s = self.label.get_untracked();
140                ctx.draw_text(
141                    &s,
142                    (ctx.rect.left(), ctx.rect.top() + 12.0),
143                    12.0,
144                    Color::WHITE,
145                );
146            }
147        }
148
149        impl_view_for_leaf!(Badge);
150
151        let sig = Signal::new(String::from("hi"));
152        let b = Badge { label: sig.clone() };
153        let _ = View::body(&b);
154        assert_eq!(b.label.get_untracked(), "hi");
155        let _ = Unit;
156    }
157}