1use teksilo_canvas::{Canvas, Rect, SizeProposal};
19
20use crate::accessibility::AccessNodeBuilder;
21use crate::build_context::BuildContext;
22use crate::widget::{LayoutContext, PaintContext, PendingChild, Widget, WidgetPlacement};
23use crate::widget_builder::HandlerSet;
24use crate::widget_id::WidgetId;
25
26#[derive(Debug)]
31pub enum TeksiBranch<L: Widget, R: Widget> {
32 L(L),
33 R(R),
34}
35
36impl<L: Widget, R: Widget> Widget for TeksiBranch<L, R> {
37 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
38 match self {
39 TeksiBranch::L(w) => w.build(ctx),
40 TeksiBranch::R(w) => w.build(ctx),
41 }
42 }
43
44 fn layout_response(
45 &self,
46 proposal: SizeProposal,
47 ctx: &LayoutContext,
48 ) -> crate::widget::LayoutResponse {
49 match self {
50 TeksiBranch::L(w) => w.layout_response(proposal, ctx),
51 TeksiBranch::R(w) => w.layout_response(proposal, ctx),
52 }
53 }
54
55 fn place_children(
56 &self,
57 bounds: Rect,
58 proposal: SizeProposal,
59 children: &mut [WidgetPlacement],
60 ctx: &LayoutContext,
61 ) {
62 match self {
63 TeksiBranch::L(w) => w.place_children(bounds, proposal, children, ctx),
64 TeksiBranch::R(w) => w.place_children(bounds, proposal, children, ctx),
65 }
66 }
67
68 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
69 match self {
70 TeksiBranch::L(w) => w.paint(bounds, canvas, ctx),
71 TeksiBranch::R(w) => w.paint(bounds, canvas, ctx),
72 }
73 }
74
75 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
76 match self {
77 TeksiBranch::L(w) => w.accessibility(builder),
78 TeksiBranch::R(w) => w.accessibility(builder),
79 }
80 }
81
82 fn accessible_title_hint(&self) -> Option<String> {
83 match self {
84 TeksiBranch::L(w) => w.accessible_title_hint(),
85 TeksiBranch::R(w) => w.accessible_title_hint(),
86 }
87 }
88
89 fn accessible_title_node(&self) -> Option<crate::widget_id::WidgetId> {
90 match self {
91 TeksiBranch::L(w) => w.accessible_title_node(),
92 TeksiBranch::R(w) => w.accessible_title_node(),
93 }
94 }
95
96 fn initial_focus_hint(&self) -> Option<WidgetId> {
97 match self {
98 TeksiBranch::L(w) => w.initial_focus_hint(),
99 TeksiBranch::R(w) => w.initial_focus_hint(),
100 }
101 }
102
103 fn children(&self) -> Vec<WidgetId> {
104 match self {
105 TeksiBranch::L(w) => w.children(),
106 TeksiBranch::R(w) => w.children(),
107 }
108 }
109
110 fn clips_children(&self) -> bool {
111 match self {
112 TeksiBranch::L(w) => w.clips_children(),
113 TeksiBranch::R(w) => w.clips_children(),
114 }
115 }
116
117 fn take_handler_set(&mut self) -> Option<HandlerSet> {
118 match self {
119 TeksiBranch::L(w) => w.take_handler_set(),
120 TeksiBranch::R(w) => w.take_handler_set(),
121 }
122 }
123}
124
125#[derive(Debug)]
130pub enum TeksiBranch3<A: Widget, B: Widget, C: Widget> {
131 A(A),
132 B(B),
133 C(C),
134}
135
136impl<A: Widget, B: Widget, C: Widget> Widget for TeksiBranch3<A, B, C> {
137 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
138 match self {
139 TeksiBranch3::A(w) => w.build(ctx),
140 TeksiBranch3::B(w) => w.build(ctx),
141 TeksiBranch3::C(w) => w.build(ctx),
142 }
143 }
144
145 fn layout_response(
146 &self,
147 proposal: SizeProposal,
148 ctx: &LayoutContext,
149 ) -> crate::widget::LayoutResponse {
150 match self {
151 TeksiBranch3::A(w) => w.layout_response(proposal, ctx),
152 TeksiBranch3::B(w) => w.layout_response(proposal, ctx),
153 TeksiBranch3::C(w) => w.layout_response(proposal, ctx),
154 }
155 }
156
157 fn place_children(
158 &self,
159 bounds: Rect,
160 proposal: SizeProposal,
161 children: &mut [WidgetPlacement],
162 ctx: &LayoutContext,
163 ) {
164 match self {
165 TeksiBranch3::A(w) => w.place_children(bounds, proposal, children, ctx),
166 TeksiBranch3::B(w) => w.place_children(bounds, proposal, children, ctx),
167 TeksiBranch3::C(w) => w.place_children(bounds, proposal, children, ctx),
168 }
169 }
170
171 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
172 match self {
173 TeksiBranch3::A(w) => w.paint(bounds, canvas, ctx),
174 TeksiBranch3::B(w) => w.paint(bounds, canvas, ctx),
175 TeksiBranch3::C(w) => w.paint(bounds, canvas, ctx),
176 }
177 }
178
179 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
180 match self {
181 TeksiBranch3::A(w) => w.accessibility(builder),
182 TeksiBranch3::B(w) => w.accessibility(builder),
183 TeksiBranch3::C(w) => w.accessibility(builder),
184 }
185 }
186
187 fn accessible_title_hint(&self) -> Option<String> {
188 match self {
189 TeksiBranch3::A(w) => w.accessible_title_hint(),
190 TeksiBranch3::B(w) => w.accessible_title_hint(),
191 TeksiBranch3::C(w) => w.accessible_title_hint(),
192 }
193 }
194
195 fn accessible_title_node(&self) -> Option<crate::widget_id::WidgetId> {
196 match self {
197 TeksiBranch3::A(w) => w.accessible_title_node(),
198 TeksiBranch3::B(w) => w.accessible_title_node(),
199 TeksiBranch3::C(w) => w.accessible_title_node(),
200 }
201 }
202
203 fn initial_focus_hint(&self) -> Option<WidgetId> {
204 match self {
205 TeksiBranch3::A(w) => w.initial_focus_hint(),
206 TeksiBranch3::B(w) => w.initial_focus_hint(),
207 TeksiBranch3::C(w) => w.initial_focus_hint(),
208 }
209 }
210
211 fn children(&self) -> Vec<WidgetId> {
212 match self {
213 TeksiBranch3::A(w) => w.children(),
214 TeksiBranch3::B(w) => w.children(),
215 TeksiBranch3::C(w) => w.children(),
216 }
217 }
218
219 fn clips_children(&self) -> bool {
220 match self {
221 TeksiBranch3::A(w) => w.clips_children(),
222 TeksiBranch3::B(w) => w.clips_children(),
223 TeksiBranch3::C(w) => w.clips_children(),
224 }
225 }
226
227 fn take_handler_set(&mut self) -> Option<HandlerSet> {
228 match self {
229 TeksiBranch3::A(w) => w.take_handler_set(),
230 TeksiBranch3::B(w) => w.take_handler_set(),
231 TeksiBranch3::C(w) => w.take_handler_set(),
232 }
233 }
234}
235
236#[derive(Debug)]
241pub enum TeksiBranch4<A: Widget, B: Widget, C: Widget, D: Widget> {
242 A(A),
243 B(B),
244 C(C),
245 D(D),
246}
247
248impl<A: Widget, B: Widget, C: Widget, D: Widget> Widget for TeksiBranch4<A, B, C, D> {
249 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
250 match self {
251 TeksiBranch4::A(w) => w.build(ctx),
252 TeksiBranch4::B(w) => w.build(ctx),
253 TeksiBranch4::C(w) => w.build(ctx),
254 TeksiBranch4::D(w) => w.build(ctx),
255 }
256 }
257
258 fn layout_response(
259 &self,
260 proposal: SizeProposal,
261 ctx: &LayoutContext,
262 ) -> crate::widget::LayoutResponse {
263 match self {
264 TeksiBranch4::A(w) => w.layout_response(proposal, ctx),
265 TeksiBranch4::B(w) => w.layout_response(proposal, ctx),
266 TeksiBranch4::C(w) => w.layout_response(proposal, ctx),
267 TeksiBranch4::D(w) => w.layout_response(proposal, ctx),
268 }
269 }
270
271 fn place_children(
272 &self,
273 bounds: Rect,
274 proposal: SizeProposal,
275 children: &mut [WidgetPlacement],
276 ctx: &LayoutContext,
277 ) {
278 match self {
279 TeksiBranch4::A(w) => w.place_children(bounds, proposal, children, ctx),
280 TeksiBranch4::B(w) => w.place_children(bounds, proposal, children, ctx),
281 TeksiBranch4::C(w) => w.place_children(bounds, proposal, children, ctx),
282 TeksiBranch4::D(w) => w.place_children(bounds, proposal, children, ctx),
283 }
284 }
285
286 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
287 match self {
288 TeksiBranch4::A(w) => w.paint(bounds, canvas, ctx),
289 TeksiBranch4::B(w) => w.paint(bounds, canvas, ctx),
290 TeksiBranch4::C(w) => w.paint(bounds, canvas, ctx),
291 TeksiBranch4::D(w) => w.paint(bounds, canvas, ctx),
292 }
293 }
294
295 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
296 match self {
297 TeksiBranch4::A(w) => w.accessibility(builder),
298 TeksiBranch4::B(w) => w.accessibility(builder),
299 TeksiBranch4::C(w) => w.accessibility(builder),
300 TeksiBranch4::D(w) => w.accessibility(builder),
301 }
302 }
303
304 fn accessible_title_hint(&self) -> Option<String> {
305 match self {
306 TeksiBranch4::A(w) => w.accessible_title_hint(),
307 TeksiBranch4::B(w) => w.accessible_title_hint(),
308 TeksiBranch4::C(w) => w.accessible_title_hint(),
309 TeksiBranch4::D(w) => w.accessible_title_hint(),
310 }
311 }
312
313 fn accessible_title_node(&self) -> Option<crate::widget_id::WidgetId> {
314 match self {
315 TeksiBranch4::A(w) => w.accessible_title_node(),
316 TeksiBranch4::B(w) => w.accessible_title_node(),
317 TeksiBranch4::C(w) => w.accessible_title_node(),
318 TeksiBranch4::D(w) => w.accessible_title_node(),
319 }
320 }
321
322 fn initial_focus_hint(&self) -> Option<WidgetId> {
323 match self {
324 TeksiBranch4::A(w) => w.initial_focus_hint(),
325 TeksiBranch4::B(w) => w.initial_focus_hint(),
326 TeksiBranch4::C(w) => w.initial_focus_hint(),
327 TeksiBranch4::D(w) => w.initial_focus_hint(),
328 }
329 }
330
331 fn children(&self) -> Vec<WidgetId> {
332 match self {
333 TeksiBranch4::A(w) => w.children(),
334 TeksiBranch4::B(w) => w.children(),
335 TeksiBranch4::C(w) => w.children(),
336 TeksiBranch4::D(w) => w.children(),
337 }
338 }
339
340 fn clips_children(&self) -> bool {
341 match self {
342 TeksiBranch4::A(w) => w.clips_children(),
343 TeksiBranch4::B(w) => w.clips_children(),
344 TeksiBranch4::C(w) => w.clips_children(),
345 TeksiBranch4::D(w) => w.clips_children(),
346 }
347 }
348
349 fn take_handler_set(&mut self) -> Option<HandlerSet> {
350 match self {
351 TeksiBranch4::A(w) => w.take_handler_set(),
352 TeksiBranch4::B(w) => w.take_handler_set(),
353 TeksiBranch4::C(w) => w.take_handler_set(),
354 TeksiBranch4::D(w) => w.take_handler_set(),
355 }
356 }
357}
358
359pub trait IntoTeksiChild {
368 fn into_pending(self) -> PendingChild;
369}
370
371impl<W: Widget + 'static> IntoTeksiChild for W {
372 fn into_pending(self) -> PendingChild {
373 PendingChild::Deferred(Box::new(self))
374 }
375}
376
377impl IntoTeksiChild for WidgetId {
378 fn into_pending(self) -> PendingChild {
379 PendingChild::Id(self)
380 }
381}
382
383pub trait IntoTeksiCondition {
403 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
404 self,
405 child: W,
406 ctx: &mut crate::build_context::BuildContext,
407 ) -> Option<WidgetId>;
408}
409
410impl IntoTeksiCondition for bool {
411 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
412 self,
413 child: W,
414 ctx: &mut crate::build_context::BuildContext,
415 ) -> Option<WidgetId> {
416 if self { Some(ctx.add(child)) } else { None }
417 }
418}
419
420impl IntoTeksiCondition for crate::signal::Signal<bool> {
421 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
422 self,
423 child: W,
424 ctx: &mut crate::build_context::BuildContext,
425 ) -> Option<WidgetId> {
426 let id = ctx.add(child);
427 ctx.visible_when(id, self);
428 Some(id)
429 }
430}
431
432impl IntoTeksiCondition for crate::signal::Prop<bool> {
433 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
434 self,
435 child: W,
436 ctx: &mut crate::build_context::BuildContext,
437 ) -> Option<WidgetId> {
438 let id = ctx.add(child);
439 ctx.visible_when(id, self);
440 Some(id)
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447 use crate::test_widgets::FillWidget;
448 use crate::widget_tree::WidgetTree;
449 use teksilo_canvas::SizeProposal;
450 use teksilo_tokens::Color;
451
452 #[test]
453 fn teksilo_branch_dispatches_to_active_variant() {
454 let mut tree_l = WidgetTree::new();
457 let id_l = tree_l.add(TeksiBranch::<FillWidget, FillWidget>::L(
458 FillWidget::new().background(Color::RED),
459 ));
460 tree_l.layout(SizeProposal::exact(100.0, 50.0));
461 assert!((tree_l.bounds(id_l).width - 100.0).abs() < 0.01);
462
463 let mut tree_r = WidgetTree::new();
464 let id_r = tree_r.add(TeksiBranch::<FillWidget, FillWidget>::R(
465 FillWidget::new().background(Color::BLUE),
466 ));
467 tree_r.layout(SizeProposal::exact(80.0, 40.0));
468 assert!((tree_r.bounds(id_r).width - 80.0).abs() < 0.01);
469 }
470
471 #[test]
472 fn teksilo_branch3_dispatches_to_active_variant() {
473 let mut tree = WidgetTree::new();
474 let id = tree.add(TeksiBranch3::<FillWidget, FillWidget, FillWidget>::B(
475 FillWidget::new(),
476 ));
477 tree.layout(SizeProposal::exact(120.0, 60.0));
478 assert!((tree.bounds(id).width - 120.0).abs() < 0.01);
479 }
480
481 #[test]
482 fn into_teksilo_child_routes_widget_to_deferred() {
483 let pending = FillWidget::new().into_pending();
484 assert!(matches!(pending, PendingChild::Deferred(_)));
485 }
486
487 #[test]
488 fn into_teksilo_child_routes_widget_id_to_id() {
489 let mut tree = WidgetTree::new();
490 let leaf = tree.add(FillWidget::new());
491 let pending = leaf.into_pending();
492 match pending {
493 PendingChild::Id(id) => assert_eq!(id, leaf),
494 _ => panic!("expected PendingChild::Id"),
495 }
496 }
497}