Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 34 kB View raw
1use core::time::Duration; 2use std::sync::Arc; 3 4use bone_types::IconId; 5use uom::si::angle::degree; 6use uom::si::f64::{Angle, Length}; 7use uom::si::length::millimeter; 8 9use crate::a11y::AccessTreeBuilder; 10use crate::focus::FocusManager; 11use crate::frame::FrameCtx; 12use crate::hit_test::{HitFrame, HitState, Interaction, InteractionState}; 13use crate::hotkey::HotkeyTable; 14use crate::input::{FrameInstant, InputSnapshot}; 15use crate::layout::{Axis, LayoutPos, LayoutPx, LayoutRect, LayoutSize}; 16use crate::raster::{CanvasPx, CanvasSize}; 17use crate::strings::{StringKey, StringTable}; 18use crate::theme::Theme; 19use crate::widget_id::{WidgetId, WidgetKey}; 20use crate::widgets::{ 21 AlwaysValid, AngleEditor, BoolEditor, Button, ButtonState, ButtonVariant, Checkbox, 22 CheckboxState, ConfirmationDialog, ContextMenu, Dialog, DialogButton, Dropdown, DropdownItem, 23 DropdownState, FilePickerDialog, FilePickerEntry, FilePickerLabels, FilePickerMode, 24 FilePickerState, HotkeyCapture, HotkeyCaptureState, LabelText, LengthEditor, ListItem, 25 ListView, ListViewState, MemoryClipboard, Menu, MenuBar, MenuBarEntry, MenuBarState, MenuItem, 26 MenuState, Modal, NumericInput, Panel, PanelState, PanelTitlebar, PanelVariant, PropertyGrid, 27 PropertyOption, PropertyPaneHeader, PropertyRow, RadioGroup, RadioOption, Ribbon, RibbonGroup, 28 RibbonIconSize, RibbonTab, Scrollbar, SelectionEditor, Slider, SliderRange, SliderStep, 29 StatusAlign, StatusBar, StatusItem, Tab, Table, TableColumn, TableRow, TableState, Tabs, 30 TabsOrientation, TextEditor, TextInput, TextInputState, Toast, ToastKind, ToastState, 31 ToggleButton, Toolbar, ToolbarItem, Tooltip, TooltipPlacement, TooltipState, TreeNode, 32 TreeView, TreeViewState, WidgetPaint, show_button, show_checkbox, show_confirmation, 33 show_context_menu, show_dialog, show_dropdown, show_file_picker, show_hotkey_capture, 34 show_list_view, show_menu, show_menu_bar, show_modal, show_panel, show_parsed_input, 35 show_property_grid, show_property_pane_header, show_radio_group, show_ribbon, show_scrollbar, 36 show_slider, show_status_bar, show_table, show_tabs, show_text_input, show_toast, 37 show_toggle_button, show_toolbar, show_tooltip, show_tree_view, 38}; 39 40pub const GALLERY_LABEL: StringKey = StringKey::new("gallery.label"); 41 42const fn gallery_picker_labels() -> FilePickerLabels { 43 FilePickerLabels { 44 title: GALLERY_LABEL, 45 confirm: GALLERY_LABEL, 46 list: GALLERY_LABEL, 47 filename_placeholder: GALLERY_LABEL, 48 } 49} 50pub const GALLERY_FRAME_NOW: FrameInstant = FrameInstant::from_duration(Duration::from_secs(1)); 51pub const GALLERY_CANVAS: CanvasSize = CanvasSize::new(CanvasPx::new(1400), CanvasPx::new(2400)); 52const TOOLTIP_ANCHOR_KEY: &str = "button"; 53 54pub type StoryPath = &'static [&'static str]; 55 56#[derive(Copy, Clone, Debug, PartialEq, Eq)] 57pub struct IndexedChild { 58 pub parent: StoryPath, 59 pub key: &'static str, 60 pub count: u64, 61} 62 63#[derive(Copy, Clone, Debug, PartialEq, Eq)] 64pub struct Story { 65 pub key: &'static str, 66 pub parent: Option<&'static str>, 67 pub kind: StoryKind, 68 pub extras: &'static [StoryPath], 69 pub indexed: &'static [IndexedChild], 70} 71 72impl Story { 73 const fn root(key: &'static str, kind: StoryKind) -> Self { 74 Self { 75 key, 76 parent: None, 77 kind, 78 extras: &[], 79 indexed: &[], 80 } 81 } 82 83 const fn root_with(key: &'static str, kind: StoryKind, extras: &'static [StoryPath]) -> Self { 84 Self { 85 key, 86 parent: None, 87 kind, 88 extras, 89 indexed: &[], 90 } 91 } 92 93 pub fn ids(self) -> impl Iterator<Item = WidgetId> { 94 let extras = self.extras.iter().copied().map(path_id); 95 let indexed = self.indexed.iter().flat_map(|child| { 96 let base = path_id(child.parent); 97 (0..child.count).map(move |index| base.child_indexed(WidgetKey::new(child.key), index)) 98 }); 99 core::iter::once(story_id(self)) 100 .chain(extras) 101 .chain(indexed) 102 } 103} 104 105#[derive(Copy, Clone, Debug, PartialEq, Eq)] 106pub enum StoryKind { 107 InputPrimitive, 108 Composite, 109 Overlay, 110} 111 112pub const STORIES: &[Story] = &[ 113 Story::root(TOOLTIP_ANCHOR_KEY, StoryKind::InputPrimitive), 114 Story::root("button_secondary", StoryKind::InputPrimitive), 115 Story::root("button_destructive", StoryKind::InputPrimitive), 116 Story::root("button_ghost", StoryKind::InputPrimitive), 117 Story::root("button_icon", StoryKind::InputPrimitive), 118 Story::root("button_disabled", StoryKind::InputPrimitive), 119 Story::root("button_loading", StoryKind::InputPrimitive), 120 Story::root("check", StoryKind::InputPrimitive), 121 Story::root("check_unchecked", StoryKind::InputPrimitive), 122 Story::root("check_indeterminate", StoryKind::InputPrimitive), 123 Story::root("toggle", StoryKind::InputPrimitive), 124 Story::root("toggle_off", StoryKind::InputPrimitive), 125 Story::root_with("radio_a", StoryKind::InputPrimitive, &[&["radio_b"]]), 126 Story::root("slider", StoryKind::InputPrimitive), 127 Story::root("scrollbar", StoryKind::InputPrimitive), 128 Story::root_with("tabs", StoryKind::InputPrimitive, &[&["tab_a"], &["tab_b"]]), 129 Story::root_with("status_bar", StoryKind::Composite, &[&["status_item"]]), 130 Story::root_with("panel", StoryKind::Composite, &[&["panel", "titlebar"]]), 131 Story::root_with( 132 "panel_collapsed", 133 StoryKind::Composite, 134 &[&["panel_collapsed", "titlebar"]], 135 ), 136 Story { 137 key: "dropdown", 138 parent: None, 139 kind: StoryKind::InputPrimitive, 140 extras: &[&["dropdown", "popup"]], 141 indexed: &[IndexedChild { 142 parent: &["dropdown"], 143 key: "item", 144 count: 2, 145 }], 146 }, 147 Story::root("text_input", StoryKind::InputPrimitive), 148 Story::root("numeric_input", StoryKind::InputPrimitive), 149 Story::root("hotkey_capture", StoryKind::InputPrimitive), 150 Story::root_with( 151 "list_view", 152 StoryKind::Composite, 153 &[&["list_a"], &["list_b"]], 154 ), 155 Story::root_with( 156 "table", 157 StoryKind::Composite, 158 &[ 159 &["col_a"], 160 &["col_b"], 161 &["row_a"], 162 &["row_b"], 163 &["col_a", "resize"], 164 &["col_b", "resize"], 165 ], 166 ), 167 Story::root_with( 168 "tree_view", 169 StoryKind::Composite, 170 &[ 171 &["tree_root"], 172 &["tree_child"], 173 &["tree_root", "disclosure"], 174 ], 175 ), 176 Story::root_with( 177 "property_grid", 178 StoryKind::Composite, 179 &[ 180 &["prop_text"], 181 &["prop_bool"], 182 &["prop_select"], 183 &["prop_length"], 184 &["prop_angle"], 185 ], 186 ), 187 Story::root_with( 188 "property_header", 189 StoryKind::Composite, 190 &[&["pane_accept"], &["pane_cancel"]], 191 ), 192 Story::root_with( 193 "toolbar", 194 StoryKind::Composite, 195 &[&["toolbar_a"], &["toolbar_b"]], 196 ), 197 Story::root_with( 198 "ribbon", 199 StoryKind::Composite, 200 &[ 201 &["ribbon_tab"], 202 &["ribbon_tool_a"], 203 &["ribbon_tool_b"], 204 &["ribbon_group"], 205 &["ribbon", "tabs"], 206 &["ribbon_group", "toolbar"], 207 ], 208 ), 209 Story::root_with( 210 "menu", 211 StoryKind::Composite, 212 &[&["menu_action"], &["menu_submenu"]], 213 ), 214 Story::root_with("context_menu", StoryKind::Composite, &[&["context_action"]]), 215 Story::root_with("menu_bar", StoryKind::Composite, &[&["menubar_entry"]]), 216 Story::root_with("toast", StoryKind::Overlay, &[&["toast", "close"]]), 217 Story::root_with( 218 "toast_success", 219 StoryKind::Overlay, 220 &[&["toast_success", "close"]], 221 ), 222 Story::root_with( 223 "toast_warning", 224 StoryKind::Overlay, 225 &[&["toast_warning", "close"]], 226 ), 227 Story::root_with( 228 "toast_danger", 229 StoryKind::Overlay, 230 &[&["toast_danger", "close"]], 231 ), 232 Story::root("modal", StoryKind::Overlay), 233 Story::root_with( 234 "dialog", 235 StoryKind::Overlay, 236 &[&["dialog_ok"], &["dialog_cancel"]], 237 ), 238 Story::root_with( 239 "confirmation", 240 StoryKind::Overlay, 241 &[&["confirmation", "confirm"], &["confirmation", "cancel"]], 242 ), 243 Story::root_with( 244 "file_picker", 245 StoryKind::Overlay, 246 &[ 247 &["picker_entry"], 248 &["file_picker", "confirm"], 249 &["file_picker", "cancel"], 250 &["file_picker", "list"], 251 &["file_picker", "filename"], 252 ], 253 ), 254 Story::root_with( 255 "file_picker_open", 256 StoryKind::Overlay, 257 &[ 258 &["picker_entry_open"], 259 &["file_picker_open", "confirm"], 260 &["file_picker_open", "cancel"], 261 &["file_picker_open", "list"], 262 ], 263 ), 264 Story { 265 key: "tooltip", 266 parent: Some(TOOLTIP_ANCHOR_KEY), 267 kind: StoryKind::Overlay, 268 extras: &[], 269 indexed: &[], 270 }, 271]; 272 273#[must_use] 274pub fn path_id(path: &[&'static str]) -> WidgetId { 275 path.iter() 276 .fold(WidgetId::ROOT, |acc, seg| acc.child(WidgetKey::new(seg))) 277} 278 279#[must_use] 280pub fn story_id(story: Story) -> WidgetId { 281 let base = match story.parent { 282 Some(parent) => WidgetId::ROOT.child(WidgetKey::new(parent)), 283 None => WidgetId::ROOT, 284 }; 285 base.child(WidgetKey::new(story.key)) 286} 287 288#[must_use] 289pub fn declared_ids() -> std::collections::BTreeSet<WidgetId> { 290 STORIES.iter().copied().flat_map(Story::ids).collect() 291} 292 293#[must_use] 294pub fn id(name: &'static str) -> WidgetId { 295 path_id(&[name]) 296} 297 298fn rect(x: f32, y: f32, w: f32, h: f32) -> LayoutRect { 299 LayoutRect::new( 300 LayoutPos::new(LayoutPx::new(x), LayoutPx::new(y)), 301 LayoutSize::new(LayoutPx::new(w), LayoutPx::new(h)), 302 ) 303} 304 305#[derive(Copy, Clone, Debug, PartialEq, Eq)] 306enum Choice { 307 A, 308 B, 309} 310 311pub struct GalleryState { 312 pub panel: PanelState, 313 pub panel_collapsed: PanelState, 314 pub dropdown: DropdownState, 315 pub text_input: TextInputState, 316 pub numeric_input: TextInputState, 317 pub clipboard: MemoryClipboard, 318 pub list: ListViewState, 319 pub table: TableState, 320 pub tree: TreeViewState, 321 pub menu: MenuState, 322 pub context_menu: MenuState, 323 pub menu_bar: MenuBarState, 324 pub tooltip: TooltipState, 325 pub toast_info: ToastState, 326 pub toast_success: ToastState, 327 pub toast_warning: ToastState, 328 pub toast_danger: ToastState, 329 pub file_picker: FilePickerState, 330 pub file_picker_open: FilePickerState, 331 pub property_text: TextEditor, 332 pub property_bool: BoolEditor, 333 pub property_select: SelectionEditor, 334 pub property_length: LengthEditor, 335 pub property_angle: AngleEditor, 336 pub hotkey: HotkeyCaptureState, 337} 338 339impl Default for GalleryState { 340 fn default() -> Self { 341 Self::new() 342 } 343} 344 345impl GalleryState { 346 #[must_use] 347 pub fn new() -> Self { 348 let mut tree = TreeViewState::default(); 349 tree.expanded.insert(id("tree_root")); 350 tree.selection.insert(id("tree_child")); 351 Self { 352 panel: PanelState::open(), 353 panel_collapsed: PanelState::collapsed(), 354 dropdown: DropdownState { 355 open: true, 356 highlighted: Some(0), 357 ..DropdownState::closed() 358 }, 359 text_input: TextInputState::from_text("Hello"), 360 numeric_input: TextInputState::from_text("42"), 361 clipboard: MemoryClipboard::default(), 362 list: ListViewState::default(), 363 table: TableState::default(), 364 tree, 365 menu: MenuState::default(), 366 context_menu: MenuState::default(), 367 menu_bar: MenuBarState::default(), 368 tooltip: TooltipState { 369 hover_began: Some(FrameInstant::ZERO), 370 ..TooltipState::default() 371 }, 372 toast_info: ToastState::fresh(), 373 toast_success: ToastState::fresh(), 374 toast_warning: ToastState::fresh(), 375 toast_danger: ToastState::fresh(), 376 file_picker: FilePickerState::default(), 377 file_picker_open: FilePickerState::default(), 378 property_text: TextEditor::new("name"), 379 property_bool: BoolEditor::new(true), 380 property_select: SelectionEditor::new( 381 vec![ 382 PropertyOption { 383 label: StringKey::new("gallery.option_a"), 384 }, 385 PropertyOption { 386 label: StringKey::new("gallery.option_b"), 387 }, 388 ], 389 Some(0), 390 ), 391 property_length: LengthEditor::new(Length::new::<millimeter>(10.0)), 392 property_angle: AngleEditor::new(Angle::new::<degree>(45.0)), 393 hotkey: HotkeyCaptureState::default(), 394 } 395 } 396} 397 398#[must_use] 399pub fn render(theme: Arc<Theme>, state: &mut GalleryState) -> Vec<WidgetPaint> { 400 let mut focus = FocusManager::new(); 401 let mut a11y = AccessTreeBuilder::new(); 402 render_with(theme, state, &mut focus, &mut a11y, StringTable::empty()) 403} 404 405#[must_use] 406pub fn render_with( 407 theme: Arc<Theme>, 408 state: &mut GalleryState, 409 focus: &mut FocusManager, 410 a11y: &mut AccessTreeBuilder, 411 strings: &StringTable, 412) -> Vec<WidgetPaint> { 413 let table = HotkeyTable::new(); 414 let mut hits = HitFrame::new(); 415 let prev = prev_hit_state(); 416 let mut input = InputSnapshot::idle(GALLERY_FRAME_NOW); 417 let mut paint = Vec::new(); 418 let mut shaper = bone_text::Shaper::new(); 419 420 let mut ctx = FrameCtx::new( 421 theme, 422 &mut input, 423 focus, 424 &table, 425 strings, 426 &mut hits, 427 &prev, 428 a11y, 429 &mut shaper, 430 ); 431 render_basics(&mut ctx, state, &mut paint); 432 render_inputs(&mut ctx, state, &mut paint); 433 render_collections(&mut ctx, state, &mut paint); 434 render_chrome(&mut ctx, state, &mut paint); 435 render_state_variants(&mut ctx, state, &mut paint); 436 render_overlays(&mut ctx, state, &mut paint); 437 paint 438} 439 440fn prev_hit_state() -> HitState { 441 let mut state = HitState::new(); 442 let hover = Interaction { 443 state: InteractionState::NONE.with(InteractionState::HOVER, true), 444 ..Interaction::idle() 445 }; 446 let anchor = id(TOOLTIP_ANCHOR_KEY); 447 state.interactions.insert(anchor, hover); 448 state.hovered = Some(anchor); 449 state 450} 451 452#[allow( 453 clippy::too_many_lines, 454 reason = "gallery renders many widgets in one frame" 455)] 456fn render_basics(ctx: &mut FrameCtx<'_>, state: &mut GalleryState, paint: &mut Vec<WidgetPaint>) { 457 let response = show_button( 458 ctx, 459 Button::new( 460 id("button"), 461 rect(0.0, 0.0, 80.0, 24.0), 462 GALLERY_LABEL, 463 ButtonVariant::Primary, 464 ), 465 ); 466 paint.extend(response.paint); 467 let response = show_checkbox( 468 ctx, 469 Checkbox::new( 470 id("check"), 471 rect(0.0, 28.0, 80.0, 20.0), 472 GALLERY_LABEL, 473 CheckboxState::Checked, 474 ), 475 ); 476 paint.extend(response.paint); 477 let response = show_toggle_button( 478 ctx, 479 ToggleButton::new( 480 id("toggle"), 481 rect(0.0, 52.0, 80.0, 20.0), 482 GALLERY_LABEL, 483 true, 484 ), 485 ); 486 paint.extend(response.paint); 487 let response = show_radio_group( 488 ctx, 489 RadioGroup::new( 490 vec![ 491 RadioOption { 492 id: id("radio_a"), 493 rect: rect(0.0, 76.0, 80.0, 20.0), 494 label: GALLERY_LABEL, 495 value: Choice::A, 496 }, 497 RadioOption { 498 id: id("radio_b"), 499 rect: rect(0.0, 100.0, 80.0, 20.0), 500 label: GALLERY_LABEL, 501 value: Choice::B, 502 }, 503 ], 504 Choice::A, 505 ), 506 ); 507 paint.extend(response.paint); 508 let Ok(slider_range) = SliderRange::try_new(0.0_f64, 10.0) else { 509 unreachable!("slider range"); 510 }; 511 let Ok(slider_step) = SliderStep::try_new(1.0_f64) else { 512 unreachable!("slider step"); 513 }; 514 let response = show_slider( 515 ctx, 516 Slider::new( 517 id("slider"), 518 rect(0.0, 124.0, 200.0, 18.0), 519 GALLERY_LABEL, 520 5.0_f64, 521 slider_range, 522 slider_step, 523 ), 524 ); 525 paint.extend(response.paint); 526 let tab_items = [ 527 Tab::new(id("tab_a"), rect(0.0, 144.0, 80.0, 24.0), GALLERY_LABEL) 528 .with_icon(IconId::TabTree), 529 Tab::new(id("tab_b"), rect(80.0, 144.0, 80.0, 24.0), GALLERY_LABEL), 530 ]; 531 let response = show_tabs( 532 ctx, 533 Tabs::new( 534 id("tabs"), 535 TabsOrientation::Top, 536 GALLERY_LABEL, 537 &tab_items, 538 id("tab_a"), 539 ), 540 ); 541 paint.extend(response.paint); 542 let status_items = [ 543 StatusItem::new( 544 id("status_item"), 545 GALLERY_LABEL, 546 StatusAlign::Start, 547 LayoutPx::new(80.0), 548 ) 549 .interactive(true), 550 StatusItem::new( 551 id("status_unit"), 552 GALLERY_LABEL, 553 StatusAlign::End, 554 LayoutPx::new(48.0), 555 ), 556 StatusItem::new( 557 id("status_mode"), 558 GALLERY_LABEL, 559 StatusAlign::End, 560 LayoutPx::new(48.0), 561 ), 562 ]; 563 let response = show_status_bar( 564 ctx, 565 StatusBar::new( 566 id("status_bar"), 567 rect(0.0, 168.0, 200.0, 22.0), 568 GALLERY_LABEL, 569 &status_items, 570 ), 571 ); 572 paint.extend(response.paint); 573 let response = show_panel( 574 ctx, 575 Panel::new( 576 id("panel"), 577 rect(0.0, 192.0, 200.0, 100.0), 578 &mut state.panel, 579 ) 580 .variant(PanelVariant::Card) 581 .titlebar(PanelTitlebar { 582 label: GALLERY_LABEL, 583 height: LayoutPx::new(22.0), 584 collapsible: true, 585 }), 586 ); 587 paint.extend(response.paint); 588 let response = show_dropdown( 589 ctx, 590 Dropdown::new( 591 id("dropdown"), 592 rect(0.0, 800.0, 160.0, 24.0), 593 LayoutPx::new(20.0), 594 vec![ 595 DropdownItem { 596 value: Choice::A, 597 label: GALLERY_LABEL, 598 }, 599 DropdownItem { 600 value: Choice::B, 601 label: GALLERY_LABEL, 602 }, 603 ], 604 Some(Choice::B), 605 GALLERY_LABEL, 606 &mut state.dropdown, 607 ), 608 ); 609 paint.extend(response.paint); 610} 611 612fn render_inputs(ctx: &mut FrameCtx<'_>, state: &mut GalleryState, paint: &mut Vec<WidgetPaint>) { 613 let response = show_text_input( 614 ctx, 615 TextInput { 616 id: id("text_input"), 617 rect: rect(0.0, 324.0, 200.0, 24.0), 618 placeholder: GALLERY_LABEL, 619 state: &mut state.text_input, 620 disabled: false, 621 validator: AlwaysValid, 622 }, 623 &mut state.clipboard, 624 ); 625 paint.extend(response.paint); 626 let response = show_parsed_input::<i32, _>( 627 ctx, 628 NumericInput::<i32>::new( 629 id("numeric_input"), 630 rect(0.0, 352.0, 200.0, 24.0), 631 GALLERY_LABEL, 632 &mut state.numeric_input, 633 ), 634 &mut state.clipboard, 635 ); 636 paint.extend(response.paint); 637 let response = show_hotkey_capture( 638 ctx, 639 HotkeyCapture::new( 640 id("hotkey_capture"), 641 rect(0.0, 380.0, 200.0, 24.0), 642 GALLERY_LABEL, 643 GALLERY_LABEL, 644 &mut state.hotkey, 645 ), 646 ); 647 paint.extend(response.paint); 648 let response = show_scrollbar( 649 ctx, 650 Scrollbar::new( 651 id("scrollbar"), 652 rect(410.0, 0.0, 14.0, 140.0), 653 Axis::Vertical, 654 GALLERY_LABEL, 655 LayoutPx::new(400.0), 656 LayoutPx::new(40.0), 657 ), 658 ); 659 paint.extend(response.paint); 660} 661 662#[allow( 663 clippy::too_many_lines, 664 reason = "gallery renders many widgets in one frame" 665)] 666fn render_collections( 667 ctx: &mut FrameCtx<'_>, 668 state: &mut GalleryState, 669 paint: &mut Vec<WidgetPaint>, 670) { 671 let list_items = [ 672 ListItem { 673 id: id("list_a"), 674 label: LabelText::Key(GALLERY_LABEL), 675 }, 676 ListItem { 677 id: id("list_b"), 678 label: LabelText::Key(GALLERY_LABEL), 679 }, 680 ]; 681 let response = show_list_view( 682 ctx, 683 ListView::new( 684 id("list_view"), 685 rect(220.0, 0.0, 180.0, 60.0), 686 GALLERY_LABEL, 687 &list_items, 688 &mut state.list, 689 ), 690 ); 691 paint.extend(response.paint); 692 let columns = [ 693 TableColumn::new(id("col_a"), GALLERY_LABEL, LayoutPx::new(80.0)), 694 TableColumn::new(id("col_b"), GALLERY_LABEL, LayoutPx::new(80.0)), 695 ]; 696 let rows = [ 697 TableRow { 698 id: id("row_a"), 699 cells: [GALLERY_LABEL, GALLERY_LABEL], 700 }, 701 TableRow { 702 id: id("row_b"), 703 cells: [GALLERY_LABEL, GALLERY_LABEL], 704 }, 705 ]; 706 let response = show_table( 707 ctx, 708 Table::new( 709 id("table"), 710 rect(220.0, 64.0, 180.0, 80.0), 711 GALLERY_LABEL, 712 &columns, 713 &rows, 714 &mut state.table, 715 ), 716 ); 717 paint.extend(response.paint); 718 let tree_roots = [TreeNode::parent( 719 id("tree_root"), 720 GALLERY_LABEL, 721 vec![TreeNode::leaf(id("tree_child"), GALLERY_LABEL).with_icon(IconId::TreeSketch)], 722 ) 723 .with_icon(IconId::TreeFeature)]; 724 let response = show_tree_view( 725 ctx, 726 TreeView::new( 727 id("tree_view"), 728 rect(220.0, 152.0, 180.0, 60.0), 729 GALLERY_LABEL, 730 &tree_roots, 731 &mut state.tree, 732 ), 733 ); 734 paint.extend(response.paint); 735 let mut rows: [PropertyRow<'_>; 5] = [ 736 PropertyRow { 737 id: id("prop_text"), 738 label: StringKey::new("gallery.prop.text"), 739 editor: &mut state.property_text, 740 read_only: false, 741 }, 742 PropertyRow { 743 id: id("prop_bool"), 744 label: StringKey::new("gallery.prop.bool"), 745 editor: &mut state.property_bool, 746 read_only: false, 747 }, 748 PropertyRow { 749 id: id("prop_select"), 750 label: StringKey::new("gallery.prop.select"), 751 editor: &mut state.property_select, 752 read_only: false, 753 }, 754 PropertyRow { 755 id: id("prop_length"), 756 label: StringKey::new("gallery.prop.length"), 757 editor: &mut state.property_length, 758 read_only: false, 759 }, 760 PropertyRow { 761 id: id("prop_angle"), 762 label: StringKey::new("gallery.prop.angle"), 763 editor: &mut state.property_angle, 764 read_only: false, 765 }, 766 ]; 767 let response = show_property_grid( 768 ctx, 769 PropertyGrid::new( 770 id("property_grid"), 771 rect(220.0, 220.0, 240.0, 160.0), 772 GALLERY_LABEL, 773 &mut rows, 774 ), 775 &mut state.clipboard, 776 ); 777 paint.extend(response.paint); 778 let response = show_property_pane_header( 779 ctx, 780 PropertyPaneHeader { 781 id: id("property_header"), 782 rect: rect(240.0, 800.0, 240.0, 48.0), 783 title: GALLERY_LABEL, 784 accept_id: id("pane_accept"), 785 cancel_id: id("pane_cancel"), 786 }, 787 ); 788 paint.extend(response.paint); 789} 790 791#[allow( 792 clippy::too_many_lines, 793 reason = "gallery renders many widgets in one frame" 794)] 795fn render_chrome(ctx: &mut FrameCtx<'_>, state: &mut GalleryState, paint: &mut Vec<WidgetPaint>) { 796 let toolbar_items = [ 797 ToolbarItem::new(id("toolbar_a"), GALLERY_LABEL), 798 ToolbarItem::new(id("toolbar_b"), GALLERY_LABEL), 799 ]; 800 let response = show_toolbar( 801 ctx, 802 Toolbar::horizontal( 803 id("toolbar"), 804 rect(480.0, 0.0, 160.0, 32.0), 805 GALLERY_LABEL, 806 &toolbar_items, 807 LayoutPx::new(28.0), 808 LayoutPx::new(4.0), 809 ), 810 ); 811 paint.extend(response.paint); 812 let ribbon_toolbar_items = [ 813 ToolbarItem::new(id("ribbon_tool_a"), GALLERY_LABEL) 814 .with_icon(RibbonIconSize::Large.slot(IconId::Line)) 815 .active(true), 816 ToolbarItem::new(id("ribbon_tool_b"), GALLERY_LABEL) 817 .with_icon(RibbonIconSize::Large.slot(IconId::ExtrudedBossBase)), 818 ]; 819 let ribbon_tabs = [RibbonTab::new( 820 id("ribbon_tab"), 821 GALLERY_LABEL, 822 vec![RibbonGroup { 823 id: id("ribbon_group"), 824 label: GALLERY_LABEL, 825 items: ribbon_toolbar_items.to_vec(), 826 icon_size: RibbonIconSize::Large, 827 min_width: LayoutPx::new(80.0), 828 width: LayoutPx::new(140.0), 829 overflow_open: false, 830 overflow_label: None, 831 }], 832 )]; 833 let response = show_ribbon( 834 ctx, 835 Ribbon::new( 836 id("ribbon"), 837 rect(480.0, 36.0, 200.0, 96.0), 838 GALLERY_LABEL, 839 &ribbon_tabs, 840 id("ribbon_tab"), 841 ), 842 ); 843 paint.extend(response.paint); 844 let menu_items = vec![ 845 MenuItem::Action { 846 id: id("menu_action"), 847 label: GALLERY_LABEL, 848 shortcut: Some(LabelText::Key(GALLERY_LABEL)), 849 disabled: false, 850 }, 851 MenuItem::Separator, 852 MenuItem::Submenu { 853 id: id("menu_submenu"), 854 label: GALLERY_LABEL, 855 items: vec![MenuItem::Action { 856 id: id("menu_subaction"), 857 label: GALLERY_LABEL, 858 shortcut: None, 859 disabled: false, 860 }], 861 }, 862 ]; 863 let response = show_menu( 864 ctx, 865 Menu::new( 866 id("menu"), 867 LayoutPos::new(LayoutPx::new(480.0), LayoutPx::new(140.0)), 868 GALLERY_LABEL, 869 &menu_items, 870 &mut state.menu, 871 ), 872 ); 873 paint.extend(response.paint); 874 let context_items = vec![MenuItem::Action { 875 id: id("context_action"), 876 label: GALLERY_LABEL, 877 shortcut: None, 878 disabled: false, 879 }]; 880 let response = show_context_menu( 881 ctx, 882 ContextMenu::at_cursor( 883 id("context_menu"), 884 LayoutPos::new(LayoutPx::new(480.0), LayoutPx::new(220.0)), 885 GALLERY_LABEL, 886 &context_items, 887 &mut state.context_menu, 888 ), 889 ); 890 paint.extend(response.paint); 891 let menu_bar_entries = [MenuBarEntry { 892 id: id("menubar_entry"), 893 label: GALLERY_LABEL, 894 items: vec![MenuItem::Action { 895 id: id("menubar_action"), 896 label: GALLERY_LABEL, 897 shortcut: None, 898 disabled: false, 899 }], 900 }]; 901 let response = show_menu_bar( 902 ctx, 903 MenuBar::new( 904 id("menu_bar"), 905 rect(480.0, 280.0, 200.0, 24.0), 906 GALLERY_LABEL, 907 &menu_bar_entries, 908 &mut state.menu_bar, 909 ), 910 ); 911 paint.extend(response.paint); 912} 913 914const BUTTON_VARIANTS: [(&str, ButtonVariant, ButtonState); 6] = [ 915 ( 916 "button_secondary", 917 ButtonVariant::Secondary, 918 ButtonState::Idle, 919 ), 920 ( 921 "button_destructive", 922 ButtonVariant::Destructive, 923 ButtonState::Idle, 924 ), 925 ("button_ghost", ButtonVariant::Ghost, ButtonState::Idle), 926 ("button_icon", ButtonVariant::IconOnly, ButtonState::Idle), 927 ( 928 "button_disabled", 929 ButtonVariant::Primary, 930 ButtonState::Disabled, 931 ), 932 ( 933 "button_loading", 934 ButtonVariant::Primary, 935 ButtonState::Loading, 936 ), 937]; 938 939const CHECKBOX_VARIANTS: [(&str, CheckboxState); 2] = [ 940 ("check_unchecked", CheckboxState::Unchecked), 941 ("check_indeterminate", CheckboxState::Indeterminate), 942]; 943 944const TOAST_VARIANTS: [(&str, ToastKind, f32); 3] = [ 945 ("toast_success", ToastKind::Success, 620.0), 946 ("toast_warning", ToastKind::Warning, 672.0), 947 ("toast_danger", ToastKind::Danger, 724.0), 948]; 949 950#[allow( 951 clippy::cast_precision_loss, 952 reason = "i bounded by small const arrays, far below f32 precision boundary" 953)] 954fn variant_x(i: usize) -> f32 { 955 (i as f32) * 84.0 956} 957 958fn render_state_variants( 959 ctx: &mut FrameCtx<'_>, 960 state: &mut GalleryState, 961 paint: &mut Vec<WidgetPaint>, 962) { 963 BUTTON_VARIANTS 964 .iter() 965 .enumerate() 966 .for_each(|(i, (key, variant, button_state))| { 967 let response = show_button( 968 ctx, 969 Button::new( 970 id(key), 971 rect(variant_x(i), 412.0, 80.0, 24.0), 972 GALLERY_LABEL, 973 *variant, 974 ) 975 .with_state(*button_state), 976 ); 977 paint.extend(response.paint); 978 }); 979 CHECKBOX_VARIANTS 980 .iter() 981 .enumerate() 982 .for_each(|(i, (key, cb_state))| { 983 let response = show_checkbox( 984 ctx, 985 Checkbox::new( 986 id(key), 987 rect(variant_x(i), 440.0, 80.0, 20.0), 988 GALLERY_LABEL, 989 *cb_state, 990 ), 991 ); 992 paint.extend(response.paint); 993 }); 994 let response = show_toggle_button( 995 ctx, 996 ToggleButton::new( 997 id("toggle_off"), 998 rect(0.0, 468.0, 80.0, 20.0), 999 GALLERY_LABEL, 1000 false, 1001 ), 1002 ); 1003 paint.extend(response.paint); 1004 let response = show_panel( 1005 ctx, 1006 Panel::new( 1007 id("panel_collapsed"), 1008 rect(0.0, 492.0, 200.0, 100.0), 1009 &mut state.panel_collapsed, 1010 ) 1011 .titlebar(PanelTitlebar { 1012 label: GALLERY_LABEL, 1013 height: LayoutPx::new(22.0), 1014 collapsible: true, 1015 }), 1016 ); 1017 paint.extend(response.paint); 1018 let toast_states = [ 1019 &mut state.toast_success, 1020 &mut state.toast_warning, 1021 &mut state.toast_danger, 1022 ]; 1023 TOAST_VARIANTS 1024 .iter() 1025 .zip(toast_states) 1026 .for_each(|((key, kind, y), toast_state)| { 1027 let response = show_toast( 1028 ctx, 1029 Toast::new( 1030 id(key), 1031 rect(20.0, *y, 360.0, 48.0), 1032 *kind, 1033 GALLERY_LABEL, 1034 toast_state, 1035 ), 1036 ); 1037 paint.extend(response.paint); 1038 }); 1039} 1040 1041fn render_overlays(ctx: &mut FrameCtx<'_>, state: &mut GalleryState, paint: &mut Vec<WidgetPaint>) { 1042 let tooltip_paint = show_tooltip( 1043 ctx, 1044 Tooltip::new( 1045 id(TOOLTIP_ANCHOR_KEY), 1046 rect(0.0, 0.0, 80.0, 24.0), 1047 GALLERY_LABEL, 1048 TooltipPlacement::Below, 1049 LayoutSize::new(LayoutPx::new(80.0), LayoutPx::new(20.0)), 1050 ), 1051 &mut state.tooltip, 1052 ); 1053 paint.extend(tooltip_paint); 1054 let response = show_toast( 1055 ctx, 1056 Toast::new( 1057 id("toast"), 1058 rect(20.0, 560.0, 360.0, 48.0), 1059 ToastKind::Info, 1060 GALLERY_LABEL, 1061 &mut state.toast_info, 1062 ), 1063 ); 1064 paint.extend(response.paint); 1065 let (modal_response, ()) = show_modal( 1066 ctx, 1067 Modal::new( 1068 id("modal"), 1069 rect(700.0, 0.0, 600.0, 400.0), 1070 LayoutSize::new(LayoutPx::new(300.0), LayoutPx::new(200.0)), 1071 GALLERY_LABEL, 1072 ), 1073 |_ctx, _body, _paint| {}, 1074 ); 1075 paint.extend(modal_response.paint); 1076 let dialog_buttons = [ 1077 DialogButton::primary(id("dialog_ok"), GALLERY_LABEL), 1078 DialogButton::secondary(id("dialog_cancel"), GALLERY_LABEL), 1079 ]; 1080 let (dialog_response, ()) = show_dialog( 1081 ctx, 1082 Dialog::new( 1083 id("dialog"), 1084 rect(700.0, 410.0, 600.0, 400.0), 1085 LayoutSize::new(LayoutPx::new(360.0), LayoutPx::new(200.0)), 1086 GALLERY_LABEL, 1087 &dialog_buttons, 1088 ), 1089 |_ctx, _body, _paint| {}, 1090 ); 1091 paint.extend(dialog_response.paint); 1092 let response = show_confirmation( 1093 ctx, 1094 ConfirmationDialog { 1095 id: id("confirmation"), 1096 viewport: rect(700.0, 820.0, 600.0, 400.0), 1097 size: LayoutSize::new(LayoutPx::new(360.0), LayoutPx::new(180.0)), 1098 title: GALLERY_LABEL, 1099 message: GALLERY_LABEL, 1100 confirm_label: GALLERY_LABEL, 1101 cancel_label: GALLERY_LABEL, 1102 destructive: false, 1103 }, 1104 ); 1105 paint.extend(response.paint); 1106 let picker_entries = [FilePickerEntry { 1107 id: id("picker_entry"), 1108 label: LabelText::Key(GALLERY_LABEL), 1109 }]; 1110 let response = show_file_picker( 1111 ctx, 1112 FilePickerDialog::new( 1113 id("file_picker"), 1114 rect(700.0, 1240.0, 600.0, 500.0), 1115 FilePickerMode::Save, 1116 GALLERY_LABEL, 1117 &picker_entries, 1118 gallery_picker_labels(), 1119 &mut state.file_picker, 1120 ), 1121 ); 1122 paint.extend(response.paint); 1123 let open_entries = [FilePickerEntry { 1124 id: id("picker_entry_open"), 1125 label: LabelText::Key(GALLERY_LABEL), 1126 }]; 1127 let response = show_file_picker( 1128 ctx, 1129 FilePickerDialog::new( 1130 id("file_picker_open"), 1131 rect(700.0, 1820.0, 600.0, 500.0), 1132 FilePickerMode::Open, 1133 GALLERY_LABEL, 1134 &open_entries, 1135 gallery_picker_labels(), 1136 &mut state.file_picker_open, 1137 ), 1138 ); 1139 paint.extend(response.paint); 1140}