1 pub mod autocomplete;
2 pub mod button;
3 pub mod checkbox;
4 pub mod compare_times;
5 pub mod containers;
6 pub mod dropdown;
7 pub mod fan_chart;
8 pub mod filler;
9 pub mod just_draw;
10 pub mod line_plot;
11 pub mod menu;
12 mod panel;
13 pub mod persistent_split;
14 pub mod scatter_plot;
15 pub mod slider;
16 pub mod spinner;
17 pub mod text_box;
18 
19 use crate::widgets::containers::{Container, Nothing};
20 pub use crate::widgets::panel::Panel;
21 use crate::{
22     Button, Choice, Color, DeferDraw, Drawable, Dropdown, EventCtx, GeomBatch, GfxCtx, JustDraw,
23     Menu, RewriteColor, ScreenDims, ScreenPt, ScreenRectangle, TextBox,
24 };
25 use geom::{Distance, Percent, Polygon};
26 use std::collections::HashSet;
27 use stretch::geometry::{Rect, Size};
28 use stretch::node::{Node, Stretch};
29 use stretch::number::Number;
30 use stretch::style::{
31     AlignItems, Dimension, FlexDirection, FlexWrap, JustifyContent, PositionType, Style,
32 };
33 
34 /// Create a new widget by implementing this trait. You can instantiate your widget by calling
35 /// `Widget::new(Box::new(instance of your new widget))`, which gives you the usual style options.
36 pub trait WidgetImpl: downcast_rs::Downcast {
37     /// What width and height does the widget occupy? If this changes, be sure to set
38     /// `redo_layout` to true in `event`.
get_dims(&self) -> ScreenDims39     fn get_dims(&self) -> ScreenDims;
40     /// Your widget's top left corner should be here. Handle mouse events and draw appropriately.
set_pos(&mut self, top_left: ScreenPt)41     fn set_pos(&mut self, top_left: ScreenPt);
42     /// Your chance to react to an event. Any side effects outside of this widget are communicated
43     /// through the output.
event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput)44     fn event(&mut self, ctx: &mut EventCtx, output: &mut WidgetOutput);
45     /// Draw the widget. Be sure to draw relative to the top-left specified by `set_pos`.
draw(&self, g: &mut GfxCtx)46     fn draw(&self, g: &mut GfxCtx);
47     /// If a new Panel is being created to replace an older one, all widgets have the chance to
48     /// preserve state from the previous version.
can_restore(&self) -> bool49     fn can_restore(&self) -> bool {
50         false
51     }
52     /// Restore state from the previous version of this widget, with the same ID. Implementors must
53     /// downcast.
restore(&mut self, _: &mut EventCtx, _prev: &Box<dyn WidgetImpl>)54     fn restore(&mut self, _: &mut EventCtx, _prev: &Box<dyn WidgetImpl>) {
55         unreachable!()
56     }
57 }
58 
59 #[derive(Debug, PartialEq)]
60 pub enum Outcome {
61     /// An action was done
62     Clicked(String),
63     /// A dropdown, checkbox, spinner, etc changed values. Usually this triggers a refresh of
64     /// everything, so not useful to plumb along what changed.
65     Changed,
66     /// Nothing happened
67     Nothing,
68 }
69 
70 pub struct WidgetOutput {
71     /// This widget changed dimensions, so recalculate layout.
72     pub redo_layout: bool,
73     /// This widget produced an Outcome, and event handling should immediately stop. Most widgets
74     /// shouldn't set this.
75     pub outcome: Outcome,
76 }
77 
78 impl WidgetOutput {
new() -> WidgetOutput79     pub fn new() -> WidgetOutput {
80         WidgetOutput {
81             redo_layout: false,
82             outcome: Outcome::Nothing,
83         }
84     }
85 }
86 
87 downcast_rs::impl_downcast!(WidgetImpl);
88 
89 pub struct Widget {
90     // TODO pub just for Container. Just move that here?
91     pub(crate) widget: Box<dyn WidgetImpl>,
92     layout: LayoutStyle,
93     pub(crate) rect: ScreenRectangle,
94     bg: Option<Drawable>,
95     // to_geom forces this one to happen
96     bg_batch: Option<GeomBatch>,
97     id: Option<String>,
98 }
99 
100 struct LayoutStyle {
101     bg_color: Option<Color>,
102     // (thickness, color)
103     outline: Option<(f64, Color)>,
104     // If None, as round as possible
105     rounded_radius: Option<f64>,
106     style: Style,
107 }
108 
109 // Layouting
110 // TODO Maybe I just want margin, not padding. And maybe more granular controls per side. And to
111 // apply margin to everything in a row or column.
112 // TODO Row and columns feel backwards when using them.
113 impl Widget {
centered(mut self) -> Widget114     pub fn centered(mut self) -> Widget {
115         self.layout.style.align_items = AlignItems::Center;
116         self.layout.style.justify_content = JustifyContent::SpaceAround;
117         self
118     }
119 
centered_horiz(self) -> Widget120     pub fn centered_horiz(self) -> Widget {
121         Widget::row(vec![self]).centered()
122     }
123 
centered_vert(self) -> Widget124     pub fn centered_vert(self) -> Widget {
125         Widget::col(vec![self]).centered()
126     }
127 
centered_cross(mut self) -> Widget128     pub fn centered_cross(mut self) -> Widget {
129         self.layout.style.align_items = AlignItems::Center;
130         self
131     }
132 
evenly_spaced(mut self) -> Widget133     pub fn evenly_spaced(mut self) -> Widget {
134         self.layout.style.justify_content = JustifyContent::SpaceBetween;
135         self
136     }
137 
fill_height(mut self) -> Widget138     pub fn fill_height(mut self) -> Widget {
139         self.layout.style.size.height = Dimension::Percent(1.0);
140         self
141     }
142 
143     // This one is really weird. percent_width should be LESS than the max_size_percent given to
144     // the overall Panel, otherwise weird things happen.
145     // Only makes sense for rows/columns.
flex_wrap(mut self, ctx: &EventCtx, width: Percent) -> Widget146     pub fn flex_wrap(mut self, ctx: &EventCtx, width: Percent) -> Widget {
147         self.layout.style.size = Size {
148             width: Dimension::Points((ctx.canvas.window_width * width.inner()) as f32),
149             height: Dimension::Undefined,
150         };
151         self.layout.style.flex_wrap = FlexWrap::Wrap;
152         self.layout.style.justify_content = JustifyContent::SpaceAround;
153         self
154     }
155     // Only for rows/columns. Used to force table columns to line up.
force_width(mut self, width: f64) -> Widget156     pub fn force_width(mut self, width: f64) -> Widget {
157         self.layout.style.size.width = Dimension::Points(width as f32);
158         self
159     }
force_width_pct(mut self, ctx: &EventCtx, width: Percent) -> Widget160     pub fn force_width_pct(mut self, ctx: &EventCtx, width: Percent) -> Widget {
161         self.layout.style.size.width =
162             Dimension::Points((ctx.canvas.window_width * width.inner()) as f32);
163         self
164     }
165 
166     // Needed for force_width.
get_width_for_forcing(&self) -> f64167     pub fn get_width_for_forcing(&self) -> f64 {
168         self.widget.get_dims().width
169     }
170 
bg(mut self, color: Color) -> Widget171     pub fn bg(mut self, color: Color) -> Widget {
172         self.layout.bg_color = Some(color);
173         self
174     }
175 
176     // Callers have to adjust padding too, probably
outline(mut self, thickness: f64, color: Color) -> Widget177     pub fn outline(mut self, thickness: f64, color: Color) -> Widget {
178         self.layout.outline = Some((thickness, color));
179         self
180     }
fully_rounded(mut self) -> Widget181     pub fn fully_rounded(mut self) -> Widget {
182         self.layout.rounded_radius = None;
183         self
184     }
185 
186     // Things like padding don't work on many widgets, so just make a convenient way to wrap in a
187     // row/column first
container(self) -> Widget188     pub fn container(self) -> Widget {
189         Widget::row(vec![self])
190     }
191 
192     // TODO Maybe panic if we call this on a non-container
padding(mut self, pixels: usize) -> Widget193     pub fn padding(mut self, pixels: usize) -> Widget {
194         self.layout.style.padding = Rect {
195             start: Dimension::Points(pixels as f32),
196             end: Dimension::Points(pixels as f32),
197             top: Dimension::Points(pixels as f32),
198             bottom: Dimension::Points(pixels as f32),
199         };
200         self
201     }
202 
margin_above(mut self, pixels: usize) -> Widget203     pub fn margin_above(mut self, pixels: usize) -> Widget {
204         self.layout.style.margin.top = Dimension::Points(pixels as f32);
205         self
206     }
margin_below(mut self, pixels: usize) -> Widget207     pub fn margin_below(mut self, pixels: usize) -> Widget {
208         self.layout.style.margin.bottom = Dimension::Points(pixels as f32);
209         self
210     }
margin_left(mut self, pixels: usize) -> Widget211     pub fn margin_left(mut self, pixels: usize) -> Widget {
212         self.layout.style.margin.start = Dimension::Points(pixels as f32);
213         self
214     }
margin_right(mut self, pixels: usize) -> Widget215     pub fn margin_right(mut self, pixels: usize) -> Widget {
216         self.layout.style.margin.end = Dimension::Points(pixels as f32);
217         self
218     }
margin_horiz(mut self, pixels: usize) -> Widget219     pub fn margin_horiz(mut self, pixels: usize) -> Widget {
220         self.layout.style.margin.start = Dimension::Points(pixels as f32);
221         self.layout.style.margin.end = Dimension::Points(pixels as f32);
222         self
223     }
margin_vert(mut self, pixels: usize) -> Widget224     pub fn margin_vert(mut self, pixels: usize) -> Widget {
225         self.layout.style.margin.top = Dimension::Points(pixels as f32);
226         self.layout.style.margin.bottom = Dimension::Points(pixels as f32);
227         self
228     }
229 
align_left(mut self) -> Widget230     pub fn align_left(mut self) -> Widget {
231         self.layout.style.margin.end = Dimension::Auto;
232         self
233     }
align_right(mut self) -> Widget234     pub fn align_right(mut self) -> Widget {
235         self.layout.style.margin = Rect {
236             start: Dimension::Auto,
237             end: Dimension::Undefined,
238             top: Dimension::Undefined,
239             bottom: Dimension::Undefined,
240         };
241         self
242     }
align_bottom(mut self) -> Widget243     pub fn align_bottom(mut self) -> Widget {
244         self.layout.style.margin = Rect {
245             start: Dimension::Undefined,
246             end: Dimension::Undefined,
247             top: Dimension::Auto,
248             bottom: Dimension::Undefined,
249         };
250         self
251     }
252     // This doesn't count against the entire container
align_vert_center(mut self) -> Widget253     pub fn align_vert_center(mut self) -> Widget {
254         self.layout.style.margin = Rect {
255             start: Dimension::Undefined,
256             end: Dimension::Undefined,
257             top: Dimension::Auto,
258             bottom: Dimension::Auto,
259         };
260         self
261     }
262 
abs(mut self, x: f64, y: f64) -> Widget263     fn abs(mut self, x: f64, y: f64) -> Widget {
264         self.layout.style.position_type = PositionType::Absolute;
265         self.layout.style.position = Rect {
266             start: Dimension::Points(x as f32),
267             end: Dimension::Undefined,
268             top: Dimension::Points(y as f32),
269             bottom: Dimension::Undefined,
270         };
271         self
272     }
273 
named<I: Into<String>>(mut self, id: I) -> Widget274     pub fn named<I: Into<String>>(mut self, id: I) -> Widget {
275         self.id = Some(id.into());
276         self
277     }
278 }
279 
280 // Convenient?? constructors
281 impl Widget {
new(widget: Box<dyn WidgetImpl>) -> Widget282     pub fn new(widget: Box<dyn WidgetImpl>) -> Widget {
283         Widget {
284             widget,
285             layout: LayoutStyle {
286                 bg_color: None,
287                 outline: None,
288                 rounded_radius: Some(5.0),
289                 style: Style {
290                     ..Default::default()
291                 },
292             },
293             rect: ScreenRectangle::placeholder(),
294             bg: None,
295             bg_batch: None,
296             id: None,
297         }
298     }
299 
300     // TODO These are literally just convenient APIs to avoid importing JustDraw. Do we want this
301     // or not?
draw_batch(ctx: &EventCtx, batch: GeomBatch) -> Widget302     pub fn draw_batch(ctx: &EventCtx, batch: GeomBatch) -> Widget {
303         JustDraw::wrap(ctx, batch)
304     }
draw_svg<I: Into<String>>(ctx: &EventCtx, filename: I) -> Widget305     pub fn draw_svg<I: Into<String>>(ctx: &EventCtx, filename: I) -> Widget {
306         JustDraw::svg(ctx, filename.into())
307     }
draw_svg_transform(ctx: &EventCtx, filename: &str, rewrite: RewriteColor) -> Widget308     pub fn draw_svg_transform(ctx: &EventCtx, filename: &str, rewrite: RewriteColor) -> Widget {
309         JustDraw::svg_transform(ctx, filename, rewrite)
310     }
311 
312     // TODO Likewise
text_entry(ctx: &EventCtx, prefilled: String, exclusive_focus: bool) -> Widget313     pub fn text_entry(ctx: &EventCtx, prefilled: String, exclusive_focus: bool) -> Widget {
314         // TODO Hardcoded style, max chars
315         Widget::new(Box::new(TextBox::new(ctx, 50, prefilled, exclusive_focus)))
316     }
317 
318     // TODO Likewise
dropdown<T: 'static + PartialEq + Clone + std::fmt::Debug>( ctx: &EventCtx, label: &str, default_value: T, choices: Vec<Choice<T>>, ) -> Widget319     pub fn dropdown<T: 'static + PartialEq + Clone + std::fmt::Debug>(
320         ctx: &EventCtx,
321         label: &str,
322         default_value: T,
323         choices: Vec<Choice<T>>,
324     ) -> Widget {
325         Widget::new(Box::new(Dropdown::new(
326             ctx,
327             label,
328             default_value,
329             choices,
330             false,
331         )))
332         .named(label)
333         .outline(ctx.style().outline_thickness, ctx.style().outline_color)
334     }
335 
custom_row(widgets: Vec<Widget>) -> Widget336     pub fn custom_row(widgets: Vec<Widget>) -> Widget {
337         Widget::new(Box::new(Container::new(true, widgets)))
338     }
row(widgets: Vec<Widget>) -> Widget339     pub fn row(widgets: Vec<Widget>) -> Widget {
340         let mut new = Vec::new();
341         let len = widgets.len();
342         // TODO Time for that is_last iterator?
343         for (idx, w) in widgets.into_iter().enumerate() {
344             if idx == len - 1 {
345                 new.push(w);
346             } else {
347                 new.push(w.margin_right(10));
348             }
349         }
350         Widget::new(Box::new(Container::new(true, new)))
351     }
352 
custom_col(widgets: Vec<Widget>) -> Widget353     pub fn custom_col(widgets: Vec<Widget>) -> Widget {
354         Widget::new(Box::new(Container::new(false, widgets)))
355     }
col(widgets: Vec<Widget>) -> Widget356     pub fn col(widgets: Vec<Widget>) -> Widget {
357         let mut new = Vec::new();
358         let len = widgets.len();
359         // TODO Time for that is_last iterator?
360         for (idx, w) in widgets.into_iter().enumerate() {
361             if idx == len - 1 {
362                 new.push(w);
363             } else {
364                 new.push(w.margin_below(10));
365             }
366         }
367         Widget::new(Box::new(Container::new(false, new)))
368     }
369 
nothing() -> Widget370     pub fn nothing() -> Widget {
371         Widget::new(Box::new(Nothing {}))
372     }
373 
374     // Also returns the hitbox of the entire widget
to_geom(mut self, ctx: &EventCtx, exact_pct_width: Option<f64>) -> (GeomBatch, Polygon)375     pub fn to_geom(mut self, ctx: &EventCtx, exact_pct_width: Option<f64>) -> (GeomBatch, Polygon) {
376         if let Some(w) = exact_pct_width {
377             // TODO 35 is a sad magic number. By default, Panels have padding of 16, so assuming
378             // this geometry is going in one of those, it makes sense to subtract 32. But that still
379             // caused some scrolling in a test, so snip away a few more pixels.
380             self.layout.style.min_size.width =
381                 Dimension::Points((w * ctx.canvas.window_width) as f32 - 35.0);
382         }
383 
384         // Pretend we're in a Panel and basically copy recompute_layout
385         {
386             let mut stretch = Stretch::new();
387             let root = stretch
388                 .new_node(
389                     Style {
390                         ..Default::default()
391                     },
392                     Vec::new(),
393                 )
394                 .unwrap();
395 
396             let mut nodes = vec![];
397             self.get_flexbox(root, &mut stretch, &mut nodes);
398             nodes.reverse();
399 
400             let container_size = Size {
401                 width: Number::Undefined,
402                 height: Number::Undefined,
403             };
404             stretch.compute_layout(root, container_size).unwrap();
405 
406             self.apply_flexbox(&stretch, &mut nodes, 0.0, 0.0, (0.0, 0.0), ctx, true, true);
407             assert!(nodes.is_empty());
408         }
409 
410         // Now build one big batch from all of the geometry, which now has the correct top left
411         // position.
412         let hitbox = self.rect.to_polygon();
413         let mut batch = GeomBatch::new();
414         self.consume_geometry(&mut batch);
415         batch.autocrop_dims = false;
416         (batch, hitbox)
417     }
418 
horiz_separator(ctx: &mut EventCtx, pct_width: f64) -> Widget419     pub fn horiz_separator(ctx: &mut EventCtx, pct_width: f64) -> Widget {
420         Widget::draw_batch(
421             ctx,
422             GeomBatch::from(vec![(
423                 Color::WHITE,
424                 Polygon::rectangle(pct_width * ctx.canvas.window_width, 2.0),
425             )]),
426         )
427         .centered_horiz()
428     }
429 
vert_separator(ctx: &mut EventCtx, height_px: f64) -> Widget430     pub fn vert_separator(ctx: &mut EventCtx, height_px: f64) -> Widget {
431         Widget::draw_batch(
432             ctx,
433             GeomBatch::from(vec![(Color::WHITE, Polygon::rectangle(2.0, height_px))]),
434         )
435     }
436 }
437 
438 // Internals
439 impl Widget {
draw(&self, g: &mut GfxCtx)440     pub(crate) fn draw(&self, g: &mut GfxCtx) {
441         // Don't draw these yet; clipping is still in effect.
442         if self.id == Some("horiz scrollbar".to_string())
443             || self.id == Some("vert scrollbar".to_string())
444         {
445             return;
446         }
447 
448         if let Some(ref bg) = self.bg {
449             g.redraw_at(ScreenPt::new(self.rect.x1, self.rect.y1), bg);
450         }
451 
452         self.widget.draw(g);
453     }
454 
455     // Populate a flattened list of Nodes, matching the traversal order
get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec<Node>)456     fn get_flexbox(&self, parent: Node, stretch: &mut Stretch, nodes: &mut Vec<Node>) {
457         if let Some(container) = self.widget.downcast_ref::<Container>() {
458             let mut style = self.layout.style.clone();
459             style.flex_direction = if container.is_row {
460                 FlexDirection::Row
461             } else {
462                 FlexDirection::Column
463             };
464             let node = stretch.new_node(style, Vec::new()).unwrap();
465             nodes.push(node);
466             for widget in &container.members {
467                 widget.get_flexbox(node, stretch, nodes);
468             }
469             stretch.add_child(parent, node).unwrap();
470             return;
471         } else {
472             let mut style = self.layout.style.clone();
473             style.size = Size {
474                 width: Dimension::Points(self.widget.get_dims().width as f32),
475                 height: Dimension::Points(self.widget.get_dims().height as f32),
476             };
477             let node = stretch.new_node(style, Vec::new()).unwrap();
478             stretch.add_child(parent, node).unwrap();
479             nodes.push(node);
480         }
481     }
482 
483     // TODO Clean up argument passing
apply_flexbox( &mut self, stretch: &Stretch, nodes: &mut Vec<Node>, dx: f64, dy: f64, scroll_offset: (f64, f64), ctx: &EventCtx, recompute_layout: bool, defer_draw: bool, )484     fn apply_flexbox(
485         &mut self,
486         stretch: &Stretch,
487         nodes: &mut Vec<Node>,
488         dx: f64,
489         dy: f64,
490         scroll_offset: (f64, f64),
491         ctx: &EventCtx,
492         recompute_layout: bool,
493         defer_draw: bool,
494     ) {
495         let result = stretch.layout(nodes.pop().unwrap()).unwrap();
496         let x: f64 = result.location.x.into();
497         let y: f64 = result.location.y.into();
498         let width: f64 = result.size.width.into();
499         let height: f64 = result.size.height.into();
500         // Don't scroll the scrollbars
501         let top_left = if self.id == Some("horiz scrollbar".to_string())
502             || self.id == Some("vert scrollbar".to_string())
503         {
504             ScreenPt::new(x, y)
505         } else {
506             ScreenPt::new(x + dx - scroll_offset.0, y + dy - scroll_offset.1)
507         };
508         self.rect = ScreenRectangle::top_left(top_left, ScreenDims::new(width, height));
509 
510         // Assume widgets don't dynamically change, so we just upload the background once.
511         if (self.bg.is_none() || recompute_layout)
512             && (self.layout.bg_color.is_some() || self.layout.outline.is_some())
513         {
514             let mut batch = GeomBatch::new();
515             if let Some(c) = self.layout.bg_color {
516                 batch.push(
517                     c,
518                     Polygon::rounded_rectangle(width, height, self.layout.rounded_radius),
519                 );
520             }
521             if let Some((thickness, color)) = self.layout.outline {
522                 batch.push(
523                     color,
524                     Polygon::rounded_rectangle(width, height, self.layout.rounded_radius)
525                         .to_outline(Distance::meters(thickness))
526                         .unwrap(),
527                 );
528             }
529             if defer_draw {
530                 self.bg_batch = Some(batch);
531             } else {
532                 self.bg = Some(ctx.upload(batch));
533             }
534         }
535 
536         if let Some(container) = self.widget.downcast_mut::<Container>() {
537             // layout() doesn't return absolute position; it's relative to the container.
538             for widget in &mut container.members {
539                 widget.apply_flexbox(
540                     stretch,
541                     nodes,
542                     x + dx,
543                     y + dy,
544                     scroll_offset,
545                     ctx,
546                     recompute_layout,
547                     defer_draw,
548                 );
549             }
550         } else {
551             self.widget.set_pos(top_left);
552         }
553     }
554 
get_all_click_actions(&self, actions: &mut HashSet<String>)555     fn get_all_click_actions(&self, actions: &mut HashSet<String>) {
556         if let Some(btn) = self.widget.downcast_ref::<Button>() {
557             if actions.contains(&btn.action) {
558                 panic!("Two buttons in one Panel both use action {}", btn.action);
559             }
560             actions.insert(btn.action.clone());
561         } else if let Some(container) = self.widget.downcast_ref::<Container>() {
562             for w in &container.members {
563                 w.get_all_click_actions(actions);
564             }
565         }
566     }
567 
currently_hovering(&self) -> Option<&String>568     fn currently_hovering(&self) -> Option<&String> {
569         if let Some(btn) = self.widget.downcast_ref::<Button>() {
570             if btn.hovering {
571                 return Some(&btn.action);
572             }
573         } else if let Some(container) = self.widget.downcast_ref::<Container>() {
574             for w in &container.members {
575                 if let Some(a) = w.currently_hovering() {
576                     return Some(a);
577                 }
578             }
579         }
580         None
581     }
582 
restore(&mut self, ctx: &mut EventCtx, prev: &Panel)583     fn restore(&mut self, ctx: &mut EventCtx, prev: &Panel) {
584         if let Some(container) = self.widget.downcast_mut::<Container>() {
585             for w in &mut container.members {
586                 w.restore(ctx, prev);
587             }
588         } else if self.widget.can_restore() {
589             if let Some(ref other) = prev.top_level.find(self.id.as_ref().unwrap()) {
590                 self.widget.restore(ctx, &other.widget);
591             }
592         }
593     }
594 
consume_geometry(mut self, batch: &mut GeomBatch)595     fn consume_geometry(mut self, batch: &mut GeomBatch) {
596         if let Some(bg) = self.bg_batch.take() {
597             batch.append(bg.translate(self.rect.x1, self.rect.y1));
598         }
599 
600         if self.widget.is::<Container>() {
601             // downcast() consumes, so we have to do the is() check first
602             if let Ok(container) = self.widget.downcast::<Container>() {
603                 for w in container.members {
604                     w.consume_geometry(batch);
605                 }
606             }
607         } else if let Ok(defer) = self.widget.downcast::<DeferDraw>() {
608             batch.append(defer.batch.translate(defer.top_left.x, defer.top_left.y));
609         } else {
610             panic!("to_geom called on a widget tree that has something interactive");
611         }
612     }
613 
is_btn(&self, name: &str) -> bool614     pub fn is_btn(&self, name: &str) -> bool {
615         self.widget
616             .downcast_ref::<Button>()
617             .map(|btn| btn.action == name)
618             .unwrap_or(false)
619     }
620 
find(&self, name: &str) -> Option<&Widget>621     fn find(&self, name: &str) -> Option<&Widget> {
622         if self.id == Some(name.to_string()) {
623             return Some(self);
624         }
625 
626         if let Some(container) = self.widget.downcast_ref::<Container>() {
627             for widget in &container.members {
628                 if let Some(w) = widget.find(name) {
629                     return Some(w);
630                 }
631             }
632         }
633 
634         None
635     }
find_mut(&mut self, name: &str) -> Option<&mut Widget>636     fn find_mut(&mut self, name: &str) -> Option<&mut Widget> {
637         if self.id == Some(name.to_string()) {
638             return Some(self);
639         }
640 
641         if let Some(container) = self.widget.downcast_mut::<Container>() {
642             for widget in &mut container.members {
643                 if let Some(w) = widget.find_mut(name) {
644                     return Some(w);
645                 }
646             }
647         }
648 
649         None
650     }
651 
take_btn(self) -> Button652     pub(crate) fn take_btn(self) -> Button {
653         *self.widget.downcast::<Button>().ok().unwrap()
654     }
take_menu<T: 'static + Clone>(self) -> Menu<T>655     pub(crate) fn take_menu<T: 'static + Clone>(self) -> Menu<T> {
656         *self.widget.downcast::<Menu<T>>().ok().unwrap()
657     }
take_just_draw(self) -> JustDraw658     pub(crate) fn take_just_draw(self) -> JustDraw {
659         *self.widget.downcast::<JustDraw>().ok().unwrap()
660     }
661 }
662