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}