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 initial_focus_hint(&self) -> Option<WidgetId> {
90 match self {
91 TeksiBranch::L(w) => w.initial_focus_hint(),
92 TeksiBranch::R(w) => w.initial_focus_hint(),
93 }
94 }
95
96 fn children(&self) -> Vec<WidgetId> {
97 match self {
98 TeksiBranch::L(w) => w.children(),
99 TeksiBranch::R(w) => w.children(),
100 }
101 }
102
103 fn clips_children(&self) -> bool {
104 match self {
105 TeksiBranch::L(w) => w.clips_children(),
106 TeksiBranch::R(w) => w.clips_children(),
107 }
108 }
109
110 fn take_handler_set(&mut self) -> Option<HandlerSet> {
111 match self {
112 TeksiBranch::L(w) => w.take_handler_set(),
113 TeksiBranch::R(w) => w.take_handler_set(),
114 }
115 }
116}
117
118#[derive(Debug)]
123pub enum TeksiBranch3<A: Widget, B: Widget, C: Widget> {
124 A(A),
125 B(B),
126 C(C),
127}
128
129impl<A: Widget, B: Widget, C: Widget> Widget for TeksiBranch3<A, B, C> {
130 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
131 match self {
132 TeksiBranch3::A(w) => w.build(ctx),
133 TeksiBranch3::B(w) => w.build(ctx),
134 TeksiBranch3::C(w) => w.build(ctx),
135 }
136 }
137
138 fn layout_response(
139 &self,
140 proposal: SizeProposal,
141 ctx: &LayoutContext,
142 ) -> crate::widget::LayoutResponse {
143 match self {
144 TeksiBranch3::A(w) => w.layout_response(proposal, ctx),
145 TeksiBranch3::B(w) => w.layout_response(proposal, ctx),
146 TeksiBranch3::C(w) => w.layout_response(proposal, ctx),
147 }
148 }
149
150 fn place_children(
151 &self,
152 bounds: Rect,
153 proposal: SizeProposal,
154 children: &mut [WidgetPlacement],
155 ctx: &LayoutContext,
156 ) {
157 match self {
158 TeksiBranch3::A(w) => w.place_children(bounds, proposal, children, ctx),
159 TeksiBranch3::B(w) => w.place_children(bounds, proposal, children, ctx),
160 TeksiBranch3::C(w) => w.place_children(bounds, proposal, children, ctx),
161 }
162 }
163
164 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
165 match self {
166 TeksiBranch3::A(w) => w.paint(bounds, canvas, ctx),
167 TeksiBranch3::B(w) => w.paint(bounds, canvas, ctx),
168 TeksiBranch3::C(w) => w.paint(bounds, canvas, ctx),
169 }
170 }
171
172 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
173 match self {
174 TeksiBranch3::A(w) => w.accessibility(builder),
175 TeksiBranch3::B(w) => w.accessibility(builder),
176 TeksiBranch3::C(w) => w.accessibility(builder),
177 }
178 }
179
180 fn accessible_title_hint(&self) -> Option<String> {
181 match self {
182 TeksiBranch3::A(w) => w.accessible_title_hint(),
183 TeksiBranch3::B(w) => w.accessible_title_hint(),
184 TeksiBranch3::C(w) => w.accessible_title_hint(),
185 }
186 }
187
188 fn initial_focus_hint(&self) -> Option<WidgetId> {
189 match self {
190 TeksiBranch3::A(w) => w.initial_focus_hint(),
191 TeksiBranch3::B(w) => w.initial_focus_hint(),
192 TeksiBranch3::C(w) => w.initial_focus_hint(),
193 }
194 }
195
196 fn children(&self) -> Vec<WidgetId> {
197 match self {
198 TeksiBranch3::A(w) => w.children(),
199 TeksiBranch3::B(w) => w.children(),
200 TeksiBranch3::C(w) => w.children(),
201 }
202 }
203
204 fn clips_children(&self) -> bool {
205 match self {
206 TeksiBranch3::A(w) => w.clips_children(),
207 TeksiBranch3::B(w) => w.clips_children(),
208 TeksiBranch3::C(w) => w.clips_children(),
209 }
210 }
211
212 fn take_handler_set(&mut self) -> Option<HandlerSet> {
213 match self {
214 TeksiBranch3::A(w) => w.take_handler_set(),
215 TeksiBranch3::B(w) => w.take_handler_set(),
216 TeksiBranch3::C(w) => w.take_handler_set(),
217 }
218 }
219}
220
221#[derive(Debug)]
226pub enum TeksiBranch4<A: Widget, B: Widget, C: Widget, D: Widget> {
227 A(A),
228 B(B),
229 C(C),
230 D(D),
231}
232
233impl<A: Widget, B: Widget, C: Widget, D: Widget> Widget for TeksiBranch4<A, B, C, D> {
234 fn build(&mut self, ctx: &mut BuildContext) -> Vec<WidgetId> {
235 match self {
236 TeksiBranch4::A(w) => w.build(ctx),
237 TeksiBranch4::B(w) => w.build(ctx),
238 TeksiBranch4::C(w) => w.build(ctx),
239 TeksiBranch4::D(w) => w.build(ctx),
240 }
241 }
242
243 fn layout_response(
244 &self,
245 proposal: SizeProposal,
246 ctx: &LayoutContext,
247 ) -> crate::widget::LayoutResponse {
248 match self {
249 TeksiBranch4::A(w) => w.layout_response(proposal, ctx),
250 TeksiBranch4::B(w) => w.layout_response(proposal, ctx),
251 TeksiBranch4::C(w) => w.layout_response(proposal, ctx),
252 TeksiBranch4::D(w) => w.layout_response(proposal, ctx),
253 }
254 }
255
256 fn place_children(
257 &self,
258 bounds: Rect,
259 proposal: SizeProposal,
260 children: &mut [WidgetPlacement],
261 ctx: &LayoutContext,
262 ) {
263 match self {
264 TeksiBranch4::A(w) => w.place_children(bounds, proposal, children, ctx),
265 TeksiBranch4::B(w) => w.place_children(bounds, proposal, children, ctx),
266 TeksiBranch4::C(w) => w.place_children(bounds, proposal, children, ctx),
267 TeksiBranch4::D(w) => w.place_children(bounds, proposal, children, ctx),
268 }
269 }
270
271 fn paint(&self, bounds: Rect, canvas: &mut Canvas, ctx: &PaintContext) {
272 match self {
273 TeksiBranch4::A(w) => w.paint(bounds, canvas, ctx),
274 TeksiBranch4::B(w) => w.paint(bounds, canvas, ctx),
275 TeksiBranch4::C(w) => w.paint(bounds, canvas, ctx),
276 TeksiBranch4::D(w) => w.paint(bounds, canvas, ctx),
277 }
278 }
279
280 fn accessibility(&self, builder: &mut AccessNodeBuilder) {
281 match self {
282 TeksiBranch4::A(w) => w.accessibility(builder),
283 TeksiBranch4::B(w) => w.accessibility(builder),
284 TeksiBranch4::C(w) => w.accessibility(builder),
285 TeksiBranch4::D(w) => w.accessibility(builder),
286 }
287 }
288
289 fn accessible_title_hint(&self) -> Option<String> {
290 match self {
291 TeksiBranch4::A(w) => w.accessible_title_hint(),
292 TeksiBranch4::B(w) => w.accessible_title_hint(),
293 TeksiBranch4::C(w) => w.accessible_title_hint(),
294 TeksiBranch4::D(w) => w.accessible_title_hint(),
295 }
296 }
297
298 fn initial_focus_hint(&self) -> Option<WidgetId> {
299 match self {
300 TeksiBranch4::A(w) => w.initial_focus_hint(),
301 TeksiBranch4::B(w) => w.initial_focus_hint(),
302 TeksiBranch4::C(w) => w.initial_focus_hint(),
303 TeksiBranch4::D(w) => w.initial_focus_hint(),
304 }
305 }
306
307 fn children(&self) -> Vec<WidgetId> {
308 match self {
309 TeksiBranch4::A(w) => w.children(),
310 TeksiBranch4::B(w) => w.children(),
311 TeksiBranch4::C(w) => w.children(),
312 TeksiBranch4::D(w) => w.children(),
313 }
314 }
315
316 fn clips_children(&self) -> bool {
317 match self {
318 TeksiBranch4::A(w) => w.clips_children(),
319 TeksiBranch4::B(w) => w.clips_children(),
320 TeksiBranch4::C(w) => w.clips_children(),
321 TeksiBranch4::D(w) => w.clips_children(),
322 }
323 }
324
325 fn take_handler_set(&mut self) -> Option<HandlerSet> {
326 match self {
327 TeksiBranch4::A(w) => w.take_handler_set(),
328 TeksiBranch4::B(w) => w.take_handler_set(),
329 TeksiBranch4::C(w) => w.take_handler_set(),
330 TeksiBranch4::D(w) => w.take_handler_set(),
331 }
332 }
333}
334
335pub trait IntoTeksiChild {
344 fn into_pending(self) -> PendingChild;
345}
346
347impl<W: Widget + 'static> IntoTeksiChild for W {
348 fn into_pending(self) -> PendingChild {
349 PendingChild::Deferred(Box::new(self))
350 }
351}
352
353impl IntoTeksiChild for WidgetId {
354 fn into_pending(self) -> PendingChild {
355 PendingChild::Id(self)
356 }
357}
358
359pub trait IntoTeksiCondition {
379 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
380 self,
381 child: W,
382 ctx: &mut crate::build_context::BuildContext,
383 ) -> Option<WidgetId>;
384}
385
386impl IntoTeksiCondition for bool {
387 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
388 self,
389 child: W,
390 ctx: &mut crate::build_context::BuildContext,
391 ) -> Option<WidgetId> {
392 if self { Some(ctx.add(child)) } else { None }
393 }
394}
395
396impl IntoTeksiCondition for crate::signal::Signal<bool> {
397 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
398 self,
399 child: W,
400 ctx: &mut crate::build_context::BuildContext,
401 ) -> Option<WidgetId> {
402 let id = ctx.add(child);
403 ctx.visible_when(id, self);
404 Some(id)
405 }
406}
407
408impl IntoTeksiCondition for crate::signal::Prop<bool> {
409 fn teksilo_into_conditional_child<W: crate::widget::Widget + 'static>(
410 self,
411 child: W,
412 ctx: &mut crate::build_context::BuildContext,
413 ) -> Option<WidgetId> {
414 let id = ctx.add(child);
415 ctx.visible_when(id, self);
416 Some(id)
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423 use crate::test_widgets::FillWidget;
424 use crate::widget_tree::WidgetTree;
425 use teksilo_canvas::SizeProposal;
426 use teksilo_tokens::Color;
427
428 #[test]
429 fn teksilo_branch_dispatches_to_active_variant() {
430 let mut tree_l = WidgetTree::new();
433 let id_l = tree_l.add(TeksiBranch::<FillWidget, FillWidget>::L(
434 FillWidget::new().background(Color::RED),
435 ));
436 tree_l.layout(SizeProposal::exact(100.0, 50.0));
437 assert!((tree_l.bounds(id_l).width - 100.0).abs() < 0.01);
438
439 let mut tree_r = WidgetTree::new();
440 let id_r = tree_r.add(TeksiBranch::<FillWidget, FillWidget>::R(
441 FillWidget::new().background(Color::BLUE),
442 ));
443 tree_r.layout(SizeProposal::exact(80.0, 40.0));
444 assert!((tree_r.bounds(id_r).width - 80.0).abs() < 0.01);
445 }
446
447 #[test]
448 fn teksilo_branch3_dispatches_to_active_variant() {
449 let mut tree = WidgetTree::new();
450 let id = tree.add(TeksiBranch3::<FillWidget, FillWidget, FillWidget>::B(
451 FillWidget::new(),
452 ));
453 tree.layout(SizeProposal::exact(120.0, 60.0));
454 assert!((tree.bounds(id).width - 120.0).abs() < 0.01);
455 }
456
457 #[test]
458 fn into_teksilo_child_routes_widget_to_deferred() {
459 let pending = FillWidget::new().into_pending();
460 assert!(matches!(pending, PendingChild::Deferred(_)));
461 }
462
463 #[test]
464 fn into_teksilo_child_routes_widget_id_to_id() {
465 let mut tree = WidgetTree::new();
466 let leaf = tree.add(FillWidget::new());
467 let pending = leaf.into_pending();
468 match pending {
469 PendingChild::Id(id) => assert_eq!(id, leaf),
470 _ => panic!("expected PendingChild::Id"),
471 }
472 }
473}