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