Another project
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}