1use teksilo_canvas::{Canvas, Point, Rect, SizeProposal};
19
20use crate::accessibility::AccessNodeBuilder;
21use crate::build_context::BuildContext;
22use crate::widget::{
23 LayoutContext, PaintContext, PendingChild, Widget, WidgetPlacement, WidgetTreeView,
24};
25use crate::widget_builder::HandlerSet;
26use crate::widget_id::WidgetId;
27
28macro_rules! impl_widget_for_branch {
47 ($branch:ident, $($arm:ident),+) => {
48 #[deny(clippy::missing_trait_methods)]
49 impl<$($arm: Widget),+> Widget for $branch<$($arm),+> {
50 fn type_name(&self) -> &'static str {
51 match self { $($branch::$arm(w) => w.type_name()),+ }
52 }
53
54 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
55 match self { $($branch::$arm(w) => w.build(ctx)),+ }
56 }
57
58 fn layout_response(
59 &self,
60 proposal: SizeProposal,
61 ctx: &LayoutContext,
62 ) -> crate::widget::LayoutResponse {
63 match self { $($branch::$arm(w) => w.layout_response(proposal, ctx)),+ }
64 }
65
66 fn cacheable_layout(&self) -> bool {
67 match self { $($branch::$arm(w) => w.cacheable_layout()),+ }
68 }
69
70 fn place_children(
71 &self,
72 bounds: Rect,
73 proposal: SizeProposal,
74 children: &mut [WidgetPlacement],
75 ctx: &LayoutContext,
76 ) {
77 match self {
78 $($branch::$arm(w) => w.place_children(bounds, proposal, children, ctx)),+
79 }
80 }
81
82 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
83 match self { $($branch::$arm(w) => w.paint(bounds, canvas, ctx)),+ }
84 }
85
86 fn wants_after_paint(&self) -> bool {
87 match self { $($branch::$arm(w) => w.wants_after_paint()),+ }
88 }
89
90 fn after_paint(&self, view: &WidgetTreeView<'_>, ctx: &PaintContext) {
91 match self { $($branch::$arm(w) => w.after_paint(view, ctx)),+ }
92 }
93
94 fn wants_post_paint(&self) -> bool {
95 match self { $($branch::$arm(w) => w.wants_post_paint()),+ }
96 }
97
98 fn post_paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
99 match self { $($branch::$arm(w) => w.post_paint(bounds, canvas, ctx)),+ }
100 }
101
102 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
103 match self { $($branch::$arm(w) => w.accessibility(builder)),+ }
104 }
105
106 fn culls_children(&self) -> bool {
107 match self { $($branch::$arm(w) => w.culls_children()),+ }
108 }
109
110 fn wants_descendant_redirects(&self) -> bool {
111 match self { $($branch::$arm(w) => w.wants_descendant_redirects()),+ }
112 }
113
114 fn a11y_redirect_descendant(
115 &self,
116 self_id: WidgetId,
117 descendant: WidgetId,
118 ) -> Option<accesskit::NodeId> {
119 match self {
120 $($branch::$arm(w) => w.a11y_redirect_descendant(self_id, descendant)),+
121 }
122 }
123
124 fn accessible_title_hint(&self) -> Option<String> {
125 match self { $($branch::$arm(w) => w.accessible_title_hint()),+ }
126 }
127
128 fn accessible_title_node(&self) -> Option<crate::widget_id::WidgetId> {
129 match self { $($branch::$arm(w) => w.accessible_title_node()),+ }
130 }
131
132 fn initial_focus_hint(&self) -> Option<WidgetId> {
133 match self { $($branch::$arm(w) => w.initial_focus_hint()),+ }
134 }
135
136 fn focus_region_landing(&self) -> Option<WidgetId> {
137 match self { $($branch::$arm(w) => w.focus_region_landing()),+ }
138 }
139
140 fn accessibility_proxy(&self) -> Option<WidgetId> {
141 match self { $($branch::$arm(w) => w.accessibility_proxy()),+ }
142 }
143
144 fn context_menu_key_target(&self) -> Option<WidgetId> {
145 match self { $($branch::$arm(w) => w.context_menu_key_target()),+ }
146 }
147
148 fn append_context_menu_section(
149 &mut self,
150 section: Box<dyn Widget>,
151 ) -> Result<(), Box<dyn Widget>> {
152 match self { $($branch::$arm(w) => w.append_context_menu_section(section)),+ }
153 }
154
155 fn children(&self) -> Vec<WidgetId> {
156 match self { $($branch::$arm(w) => w.children()),+ }
157 }
158
159 fn accessibility_children(&self) -> Option<Vec<WidgetId>> {
160 match self { $($branch::$arm(w) => w.accessibility_children()),+ }
161 }
162
163 fn as_any(&self) -> Option<&dyn std::any::Any> {
164 match self { $($branch::$arm(w) => w.as_any()),+ }
165 }
166
167 fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
168 match self { $($branch::$arm(w) => w.as_any_mut()),+ }
169 }
170
171 fn clips_children(&self) -> bool {
172 match self { $($branch::$arm(w) => w.clips_children()),+ }
173 }
174
175 fn focus_reveal_rect(&self, bounds: Rect) -> Option<Rect> {
176 match self { $($branch::$arm(w) => w.focus_reveal_rect(bounds)),+ }
177 }
178
179 fn hit_shape(&self, local_point: Point, bounds: Rect) -> bool {
180 match self { $($branch::$arm(w) => w.hit_shape(local_point, bounds)),+ }
181 }
182
183 fn accepts_child_hit(&self, child: WidgetId, point: Point) -> bool {
184 match self { $($branch::$arm(w) => w.accepts_child_hit(child, point)),+ }
185 }
186
187 fn hit_outset(
188 &self,
189 kind: teksilo_tokens::PointerKind,
190 tokens: &teksilo_tokens::InputTokens,
191 ) -> teksilo_canvas::EdgeInsets {
192 match self { $($branch::$arm(w) => w.hit_outset(kind, tokens)),+ }
193 }
194
195 fn hit_slop(
196 &self,
197 kind: teksilo_tokens::PointerKind,
198 tokens: &teksilo_tokens::InputTokens,
199 ) -> Option<crate::pointer::hit_slop::HitSlop> {
200 match self { $($branch::$arm(w) => Widget::hit_slop(w, kind, tokens)),+ }
204 }
205
206 fn hit_distance(&self, local_point: Point, bounds: Rect) -> Option<f32> {
207 match self { $($branch::$arm(w) => w.hit_distance(local_point, bounds)),+ }
208 }
209
210 fn target_regions(&self, bounds: Rect) -> Vec<crate::partition::TargetRegion> {
211 match self { $($branch::$arm(w) => w.target_regions(bounds)),+ }
212 }
213
214 fn preserves_children_on_rebuild(&self) -> bool {
215 match self { $($branch::$arm(w) => w.preserves_children_on_rebuild()),+ }
216 }
217
218 fn tooltip_has_content(&self) -> bool {
219 match self { $($branch::$arm(w) => w.tooltip_has_content()),+ }
220 }
221
222 fn declare_shortcuts(&self) -> Vec<crate::shortcut::Shortcut> {
223 match self { $($branch::$arm(w) => w.declare_shortcuts()),+ }
224 }
225
226 fn take_handler_set(&mut self) -> Option<HandlerSet> {
227 match self { $($branch::$arm(w) => w.take_handler_set()),+ }
228 }
229 }
230 };
231}
232
233#[derive(Debug)]
238pub enum TeksiBranch<L: Widget, R: Widget> {
239 L(L),
240 R(R),
241}
242
243impl_widget_for_branch!(TeksiBranch, L, R);
244
245#[derive(Debug)]
250pub enum TeksiBranch3<A: Widget, B: Widget, C: Widget> {
251 A(A),
252 B(B),
253 C(C),
254}
255
256impl_widget_for_branch!(TeksiBranch3, A, B, C);
257
258#[derive(Debug)]
263pub enum TeksiBranch4<A: Widget, B: Widget, C: Widget, D: Widget> {
264 A(A),
265 B(B),
266 C(C),
267 D(D),
268}
269
270impl_widget_for_branch!(TeksiBranch4, A, B, C, D);
271
272pub trait IntoTeksiChild {
281 fn into_pending(self) -> PendingChild;
282}
283
284impl<W: Widget + 'static> IntoTeksiChild for W {
285 fn into_pending(self) -> PendingChild {
286 PendingChild::Deferred(Box::new(self))
287 }
288}
289
290impl IntoTeksiChild for WidgetId {
291 fn into_pending(self) -> PendingChild {
292 PendingChild::Id(self)
293 }
294}
295
296pub trait IntoTeksiCondition {
323 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
324 self,
325 child: W,
326 ctx: &mut crate::build_context::BuildContext,
327 ) -> Option<WidgetId>;
328}
329
330impl IntoTeksiCondition for bool {
331 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
332 self,
333 child: W,
334 ctx: &mut crate::build_context::BuildContext,
335 ) -> Option<WidgetId> {
336 if self { Some(ctx.add(child)) } else { None }
337 }
338}
339
340impl IntoTeksiCondition for crate::signal::Signal<bool> {
341 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
342 self,
343 child: W,
344 ctx: &mut crate::build_context::BuildContext,
345 ) -> Option<WidgetId> {
346 let id = ctx.add(child);
347 ctx.visible_when(id, self);
348 Some(id)
349 }
350}
351
352impl IntoTeksiCondition for crate::signal::Prop<bool> {
353 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
354 self,
355 child: W,
356 ctx: &mut crate::build_context::BuildContext,
357 ) -> Option<WidgetId> {
358 let id = ctx.add(child);
359 ctx.visible_when(id, self);
360 Some(id)
361 }
362}
363
364#[cfg(test)]
365mod tests {
366 use super::*;
367 use crate::test_widgets::FillWidget;
368 use crate::widget_tree::WidgetTree;
369 use teksilo_canvas::SizeProposal;
370 use teksilo_tokens::Color;
371
372 #[test]
373 fn teksilo_branch_dispatches_to_active_variant() {
374 let mut tree_l = WidgetTree::new();
377 let id_l = tree_l.add(TeksiBranch::<FillWidget, FillWidget>::L(
378 FillWidget::new().background(Color::RED),
379 ));
380 tree_l.layout(SizeProposal::exact(100.0, 50.0));
381 assert!((tree_l.bounds(id_l).width - 100.0).abs() < 0.01);
382
383 let mut tree_r = WidgetTree::new();
384 let id_r = tree_r.add(TeksiBranch::<FillWidget, FillWidget>::R(
385 FillWidget::new().background(Color::BLUE),
386 ));
387 tree_r.layout(SizeProposal::exact(80.0, 40.0));
388 assert!((tree_r.bounds(id_r).width - 80.0).abs() < 0.01);
389 }
390
391 #[test]
392 fn teksilo_branch3_dispatches_to_active_variant() {
393 let mut tree = WidgetTree::new();
394 let id = tree.add(TeksiBranch3::<FillWidget, FillWidget, FillWidget>::B(
395 FillWidget::new(),
396 ));
397 tree.layout(SizeProposal::exact(120.0, 60.0));
398 assert!((tree.bounds(id).width - 120.0).abs() < 0.01);
399 }
400
401 #[test]
402 fn into_teksilo_child_routes_widget_to_deferred() {
403 let pending = FillWidget::new().into_pending();
404 assert!(matches!(pending, PendingChild::Deferred(_)));
405 }
406
407 #[test]
408 fn into_teksilo_child_routes_widget_id_to_id() {
409 let mut tree = WidgetTree::new();
410 let leaf = tree.add(FillWidget::new());
411 let pending = leaf.into_pending();
412 match pending {
413 PendingChild::Id(id) => assert_eq!(id, leaf),
414 _ => panic!("expected PendingChild::Id"),
415 }
416 }
417}