pub struct UiRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}Fields§
§x: f32§y: f32§width: f32§height: f32Implementations§
Source§impl UiRect
impl UiRect
Sourcepub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self
pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self
Examples found in repository?
examples/showcase.rs (line 1427)
1421 fn apply_text_edit(&mut self, input: FocusedTextInput, edit: WidgetTextEdit) {
1422 self.focused_text = Some(input);
1423 if let Some(point) = edit.local_position {
1424 let style = text(13.0, color(230, 236, 246));
1425 let target_rect = edit
1426 .target_rect
1427 .unwrap_or_else(|| UiRect::new(0.0, 0.0, 320.0, 36.0));
1428 let metrics = TextInputLayoutMetrics::from_style(
1429 UiRect::new(
1430 6.0,
1431 6.0,
1432 (target_rect.width - 12.0).max(1.0),
1433 (target_rect.height - 12.0).max(1.0),
1434 ),
1435 &style,
1436 );
1437 if let Some(state) = self.text_state_mut(input) {
1438 state.move_caret_to_point(metrics, point, edit.selecting);
1439 }
1440 return;
1441 }
1442
1443 let outcome = if input.is_read_only() {
1444 self.text_state_mut(input).map(|state| {
1445 state.handle_event_with_policy(
1446 &edit.event,
1447 widgets::TextInputInteractionPolicy::read_only(),
1448 )
1449 })
1450 } else {
1451 self.text_state_mut(input)
1452 .map(|state| state.handle_event(&edit.event))
1453 };
1454 if let Some(outcome) = outcome {
1455 self.apply_text_clipboard_outcome(input, outcome);
1456 self.sync_text_input_value(input);
1457 }
1458 }
1459
1460 fn apply_text_clipboard_outcome(
1461 &mut self,
1462 input: FocusedTextInput,
1463 outcome: widgets::TextInputOutcome,
1464 ) {
1465 match outcome.clipboard {
1466 Some(widgets::TextInputClipboardAction::Copy(text))
1467 | Some(widgets::TextInputClipboardAction::Cut(text)) => {
1468 self.copy_text_to_system_clipboard(&text);
1469 self.clipboard_text = text;
1470 }
1471 Some(widgets::TextInputClipboardAction::Paste) => {
1472 let pasted = self
1473 .read_text_from_system_clipboard()
1474 .unwrap_or_else(|| self.clipboard_text.clone());
1475 if !input.is_read_only() {
1476 if let Some(state) = self.text_state_mut(input) {
1477 state.paste_text(&pasted);
1478 }
1479 }
1480 }
1481 None => {}
1482 }
1483 }
1484
1485 fn text_state_mut(&mut self, input: FocusedTextInput) -> Option<&mut TextInputState> {
1486 match input {
1487 FocusedTextInput::Editable => Some(&mut self.text),
1488 FocusedTextInput::Selectable | FocusedTextInput::SelectableHelper => {
1489 Some(&mut self.selectable_text)
1490 }
1491 FocusedTextInput::Singleline => Some(&mut self.singleline_text),
1492 FocusedTextInput::Multiline => Some(&mut self.multiline_text),
1493 FocusedTextInput::TextArea => Some(&mut self.text_area_text),
1494 FocusedTextInput::CodeEditor => Some(&mut self.code_editor_text),
1495 FocusedTextInput::Search => Some(&mut self.search_text),
1496 FocusedTextInput::Password => Some(&mut self.password_text),
1497 FocusedTextInput::SliderValue => Some(&mut self.slider_value_text),
1498 FocusedTextInput::SliderRangeLeft => Some(&mut self.slider_left_text),
1499 FocusedTextInput::SliderRangeRight => Some(&mut self.slider_right_text),
1500 FocusedTextInput::SliderStep => Some(&mut self.slider_step_text),
1501 }
1502 }
1503
1504 fn sync_text_input_value(&mut self, input: FocusedTextInput) {
1505 match input {
1506 FocusedTextInput::SliderValue => {
1507 if let Ok(value) = self.slider_value_text.text.parse::<f32>() {
1508 self.apply_slider_value_from_text(value);
1509 }
1510 }
1511 FocusedTextInput::SliderRangeLeft => {
1512 if let Ok(value) = self.slider_left_text.text.parse::<f32>() {
1513 self.apply_slider_left_from_text(value);
1514 }
1515 }
1516 FocusedTextInput::SliderRangeRight => {
1517 if let Ok(value) = self.slider_right_text.text.parse::<f32>() {
1518 self.apply_slider_right_from_text(value);
1519 }
1520 }
1521 FocusedTextInput::SliderStep => {
1522 if let Ok(value) = self.slider_step_text.text.parse::<f32>() {
1523 self.slider_step_value = value.abs().max(0.0001);
1524 if self.slider_use_steps {
1525 self.set_slider_value(widgets::round_slider_to_step(
1526 self.slider,
1527 self.slider_step(),
1528 ));
1529 }
1530 }
1531 }
1532 _ => {}
1533 }
1534 }
1535
1536 fn copy_text_to_system_clipboard(&mut self, text: &str) {
1537 if self.system_clipboard.is_none() {
1538 self.system_clipboard = create_system_clipboard();
1539 }
1540 if let Some(clipboard) = self.system_clipboard.as_mut() {
1541 if clipboard.set_text(text.to_string()).is_err() {
1542 self.system_clipboard = None;
1543 }
1544 }
1545 }
1546
1547 fn read_text_from_system_clipboard(&mut self) -> Option<String> {
1548 if self.system_clipboard.is_none() {
1549 self.system_clipboard = create_system_clipboard();
1550 }
1551 self.system_clipboard
1552 .as_mut()
1553 .and_then(|clipboard| clipboard.get_text().ok())
1554 }
1555
1556 fn apply_menu_item(&mut self, id: &str) {
1557 let menus = menu_bar_menus(self.menu_autosave, self.menu_grid);
1558 self.menu_bar.set_active_item_by_id(&menus, id);
1559 if id == "autosave" {
1560 self.menu_autosave = !self.menu_autosave;
1561 } else if id == "grid" {
1562 self.menu_grid = !self.menu_grid;
1563 }
1564 self.menu_button.close();
1565 self.image_text_menu_button.close();
1566 self.image_menu_button.close();
1567 }
1568
1569 fn apply_tree_row(&mut self, id: &str, outliner: bool) {
1570 let roots = tree_items();
1571 let state = if outliner {
1572 &mut self.outliner
1573 } else {
1574 &mut self.tree
1575 };
1576 state.activate_visible_item_id(&roots, id);
1577 }
1578
1579 fn slider_value_spec(&self) -> widgets::SliderValueSpec {
1580 let mut spec = widgets::SliderValueSpec::new(self.slider_left, self.slider_right)
1581 .logarithmic(self.slider_logarithmic)
1582 .clamping(self.slider_clamping)
1583 .smart_aim(self.slider_smart_aim);
1584 if self.slider_use_steps {
1585 spec = spec.step(self.slider_step());
1586 }
1587 spec
1588 }
1589
1590 fn set_slider_value(&mut self, value: f32) {
1591 let value = self.slider_value_spec().adjust_value(value);
1592 self.slider = value;
1593 self.slider_value_text.text = widgets::format_slider_value(value);
1594 self.slider_value_text.caret = self.slider_value_text.text.len();
1595 self.slider_value_text.selection_anchor = None;
1596 }
1597
1598 fn apply_slider_value_from_text(&mut self, value: f32) {
1599 self.slider = if self.slider_clamping == widgets::SliderClamping::Always {
1600 self.slider_value_spec().clamp(value)
1601 } else {
1602 value
1603 };
1604 }
1605
1606 fn set_slider_left(&mut self, value: f32) {
1607 self.slider_left = value.min(self.slider_right - 1.0).max(0.0);
1608 self.slider_left_text.text = widgets::format_slider_value(self.slider_left);
1609 self.slider_left_text.caret = self.slider_left_text.text.len();
1610 if self.slider_clamping == widgets::SliderClamping::Always {
1611 self.clamp_slider_to_range();
1612 }
1613 }
1614
1615 fn apply_slider_left_from_text(&mut self, value: f32) {
1616 if value < self.slider_right {
1617 self.slider_left = value.max(0.0);
1618 if self.slider_clamping == widgets::SliderClamping::Always {
1619 self.slider = self.slider.clamp(self.slider_left, self.slider_right);
1620 }
1621 }
1622 }
1623
1624 fn set_slider_right(&mut self, value: f32) {
1625 self.slider_right = value.max(self.slider_left + 1.0).min(10000.0);
1626 self.slider_right_text.text = widgets::format_slider_value(self.slider_right);
1627 self.slider_right_text.caret = self.slider_right_text.text.len();
1628 if self.slider_clamping == widgets::SliderClamping::Always {
1629 self.clamp_slider_to_range();
1630 }
1631 }
1632
1633 fn apply_slider_right_from_text(&mut self, value: f32) {
1634 if value > self.slider_left {
1635 self.slider_right = value.min(10000.0);
1636 if self.slider_clamping == widgets::SliderClamping::Always {
1637 self.slider = self.slider.clamp(self.slider_left, self.slider_right);
1638 }
1639 }
1640 }
1641
1642 fn clamp_slider_to_range(&mut self) {
1643 self.set_slider_value(self.slider.clamp(self.slider_left, self.slider_right));
1644 }
1645
1646 fn slider_step(&self) -> f32 {
1647 self.slider_step_value.abs().max(0.0001)
1648 }
1649
1650 fn view(&self, viewport: UiSize) -> UiDocument {
1651 let mut ui = UiDocument::new(root_style(viewport.width, viewport.height));
1652 ui.node_mut(ui.root).visual = UiVisual::panel(color(16, 20, 26), None, 0.0);
1653
1654 let root = ui.root;
1655 let shell = ui.add_child(
1656 root,
1657 UiNode::container(
1658 "showcase.shell",
1659 LayoutStyle::row().with_size(viewport.width, viewport.height),
1660 ),
1661 );
1662 let desktop_width = (viewport.width - RIGHT_PANEL_WIDTH).max(360.0);
1663 let desktop = ui.add_child(
1664 shell,
1665 UiNode::container(
1666 "showcase.desktop",
1667 LayoutStyle::new()
1668 .with_width(desktop_width)
1669 .with_height(viewport.height)
1670 .with_flex_shrink(1.0),
1671 )
1672 .with_visual(UiVisual::panel(color(15, 19, 25), None, 0.0)),
1673 );
1674 let controls = ui.add_child(
1675 shell,
1676 UiNode::container(
1677 "showcase.controls",
1678 LayoutStyle::column()
1679 .with_width(RIGHT_PANEL_WIDTH)
1680 .with_height(viewport.height)
1681 .with_flex_shrink(0.0)
1682 .padding(12.0)
1683 .gap(4.0),
1684 )
1685 .with_visual(UiVisual::panel(
1686 color(21, 26, 33),
1687 Some(StrokeStyle::new(color(46, 56, 70), 1.0)),
1688 0.0,
1689 )),
1690 );
1691
1692 showcase_windows(
1693 &mut ui,
1694 desktop,
1695 self,
1696 UiSize::new(desktop_width, viewport.height),
1697 );
1698 control_panel(&mut ui, controls, self, viewport.height);
1699
1700 ui
1701 }
1702}
1703
1704fn showcase_windows(
1705 ui: &mut UiDocument,
1706 desktop: UiNodeId,
1707 state: &ShowcaseState,
1708 desktop_size: UiSize,
1709) {
1710 let windows = showcase_window_descriptors(state, desktop_size);
1711 let mut options = widgets::FloatingDesktopOptions::new(desktop_size).with_layout(
1712 LayoutStyle::new()
1713 .with_width_percent(1.0)
1714 .with_height_percent(1.0),
1715 );
1716 options.base_z_index = SHOWCASE_WINDOW_Z_BASE;
1717 options.window_z_stride = SHOWCASE_WINDOW_Z_STRIDE;
1718 options.margin = 18.0;
1719 options.gap = 14.0;
1720 widgets::floating_desktop(
1721 ui,
1722 desktop,
1723 "showcase.windows",
1724 &windows,
1725 options,
1726 |ui, window, descriptor| match descriptor.id.as_str() {
1727 "labels" => labels(ui, window, state),
1728 "buttons" => buttons(ui, window, state),
1729 "checkbox" => checkbox(ui, window, state),
1730 "toggles" => toggles(ui, window, state),
1731 "slider" => slider(ui, window, state),
1732 "numeric" => numeric_inputs(ui, window, state),
1733 "text_input" => text_input(ui, window, state),
1734 "selection" => selection_widgets(ui, window, state),
1735 "menus" => menu_widgets(ui, window, state),
1736 "command_palette" => command_palette(ui, window, state),
1737 "date_picker" => date_picker(ui, window, state),
1738 "color_picker" => color_picker(ui, window, state),
1739 "color_buttons" => color_buttons(ui, window, state),
1740 "progress" => progress_indicator(ui, window, state),
1741 "lists_tables" => list_and_table_widgets(ui, window, state),
1742 "property_inspector" => property_inspector(ui, window, state),
1743 "trees" => tree_widgets(ui, window, state),
1744 "layout_widgets" => tab_split_dock_widgets(ui, window, state),
1745 "containers" => container_widgets(ui, window, state),
1746 "forms" => form_widgets(ui, window, state),
1747 "overlays" => overlay_widgets(ui, window, state),
1748 "drag_drop" => drag_drop_widgets(ui, window, state),
1749 "media" => media_widgets(ui, window),
1750 "timeline" => timeline_ruler(ui, window),
1751 "toasts" => toast_controls(ui, window, state),
1752 "popup_panel" => popup_controls(ui, window, state),
1753 "canvas" => canvas(ui, window),
1754 "styling" => styling_widgets(ui, window, state),
1755 _ => {}
1756 },
1757 );
1758 showcase_overlays(ui, desktop, state, desktop_size);
1759}
1760
1761fn showcase_overlays(
1762 ui: &mut UiDocument,
1763 desktop: UiNodeId,
1764 state: &ShowcaseState,
1765 desktop_size: UiSize,
1766) {
1767 if state.toast_visible {
1768 let overlay_width = 320.0;
1769 let overlay = ui.add_child(
1770 desktop,
1771 UiNode::container(
1772 "showcase.toast_overlay",
1773 UiNodeStyle {
1774 layout: LayoutStyle::absolute_rect(UiRect::new(
1775 (desktop_size.width - overlay_width - 18.0).max(18.0),
1776 18.0,
1777 overlay_width,
1778 180.0,
1779 ))
1780 .as_taffy_style()
1781 .clone(),
1782 clip: ClipBehavior::None,
1783 z_index: 6000,
1784 ..Default::default()
1785 },
1786 ),
1787 );
1788 let mut stack = widgets::ToastStack::new(3);
1789 stack.push_toast(
1790 widgets::Toast::new(
1791 widgets::ToastId(1),
1792 widgets::ToastSeverity::Success,
1793 "Saved",
1794 Some("All changes are written".to_string()),
1795 None,
1796 )
1797 .with_action(widgets::ToastAction::new("undo", "Undo")),
1798 );
1799 stack.push(
1800 widgets::ToastSeverity::Warning,
1801 "Autosave paused",
1802 Some("Changes are kept locally".to_string()),
1803 None,
1804 );
1805 let mut options = widgets::ToastStackOptions::default();
1806 options.z_index = 6100;
1807 widgets::toast_stack(ui, overlay, "showcase.toast_overlay.stack", &stack, options);
1808 }
1809
1810 if state.popup_open {
1811 let popup_width = 280.0;
1812 let popup_height = 110.0;
1813 let popup = widgets::popup_panel(
1814 ui,
1815 desktop,
1816 "showcase.popup_overlay",
1817 UiRect::new(
1818 (desktop_size.width - popup_width - 36.0).max(18.0),
1819 220.0_f32.min((desktop_size.height - popup_height - 18.0).max(18.0)),
1820 popup_width,
1821 popup_height,
1822 ),
1823 widgets::PopupOptions {
1824 z_index: 6100,
1825 accessibility: Some(
1826 AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup panel"),
1827 ),
1828 ..Default::default()
1829 },
1830 );
1831 let body = ui.add_child(
1832 popup,
1833 UiNode::container(
1834 "showcase.popup_overlay.body",
1835 LayoutStyle::column()
1836 .with_width_percent(1.0)
1837 .with_height_percent(1.0)
1838 .padding(12.0)
1839 .gap(8.0),
1840 ),
1841 );
1842 let header = row(ui, body, "showcase.popup_overlay.header", 8.0);
1843 widgets::label(
1844 ui,
1845 header,
1846 "showcase.popup_overlay.title",
1847 "Popup panel",
1848 text(13.0, color(240, 244, 250)),
1849 LayoutStyle::new().with_width_percent(1.0),
1850 );
1851 let mut close =
1852 widgets::ButtonOptions::new(LayoutStyle::size(28.0, 24.0)).with_action("popup.close");
1853 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
1854 close.hovered_visual = Some(button_visual(54, 70, 92));
1855 close.text_style = text(13.0, color(220, 228, 238));
1856 widgets::button(ui, header, "showcase.popup_overlay.close", "x", close);
1857 widgets::label(
1858 ui,
1859 body,
1860 "showcase.popup_overlay.body_text",
1861 "This surface is rendered as an overlay.",
1862 text(12.0, color(196, 210, 230)),
1863 LayoutStyle::new().with_width_percent(1.0),
1864 );
1865 }
1866}
1867
1868fn showcase_window_descriptors(
1869 state: &ShowcaseState,
1870 desktop_size: UiSize,
1871) -> Vec<widgets::FloatingWindowDescriptor> {
1872 let wide = (desktop_size.width - 36.0).min(720.0).max(320.0);
1873 let medium = (desktop_size.width - 36.0).min(604.0).max(300.0);
1874 let buttons_width = medium.min(620.0);
1875 let mut windows = Vec::new();
1876 push_window(
1877 &mut windows,
1878 state.windows.labels,
1879 "labels",
1880 "Labels",
1881 UiSize::new(380.0, 460.0),
1882 );
1883 push_window(
1884 &mut windows,
1885 state.windows.buttons,
1886 "buttons",
1887 "Buttons",
1888 UiSize::new(buttons_width, 220.0),
1889 );
1890 push_window(
1891 &mut windows,
1892 state.windows.checkbox,
1893 "checkbox",
1894 "Checkbox",
1895 UiSize::new(250.0, 72.0),
1896 );
1897 push_window(
1898 &mut windows,
1899 state.windows.toggles,
1900 "toggles",
1901 "Radio and toggles",
1902 UiSize::new(360.0, 320.0),
1903 );
1904 push_window(
1905 &mut windows,
1906 state.windows.slider,
1907 "slider",
1908 "Slider",
1909 UiSize::new(430.0, 560.0),
1910 );
1911 push_window(
1912 &mut windows,
1913 state.windows.numeric,
1914 "numeric",
1915 "Numeric input",
1916 UiSize::new(360.0, 180.0),
1917 );
1918 push_window(
1919 &mut windows,
1920 state.windows.text_input,
1921 "text_input",
1922 "Text input",
1923 UiSize::new(520.0, 560.0),
1924 );
1925 push_window(
1926 &mut windows,
1927 state.windows.selection,
1928 "selection",
1929 "Select controls",
1930 UiSize::new(360.0, 360.0),
1931 );
1932 push_window(
1933 &mut windows,
1934 state.windows.menus,
1935 "menus",
1936 "Menus",
1937 UiSize::new(wide, 520.0),
1938 );
1939 push_window(
1940 &mut windows,
1941 state.windows.command_palette,
1942 "command_palette",
1943 "Command palette",
1944 UiSize::new(520.0, 320.0),
1945 );
1946 push_window(
1947 &mut windows,
1948 state.windows.date_picker,
1949 "date_picker",
1950 "Date picker",
1951 UiSize::new(430.0, 390.0),
1952 );
1953 push_window(
1954 &mut windows,
1955 state.windows.color_picker,
1956 "color_picker",
1957 "Color picker",
1958 UiSize::new(340.0, 390.0),
1959 );
1960 push_window(
1961 &mut windows,
1962 state.windows.color_buttons,
1963 "color_buttons",
1964 "Color buttons",
1965 UiSize::new(430.0, 360.0),
1966 );
1967 push_window(
1968 &mut windows,
1969 state.windows.progress,
1970 "progress",
1971 "Progress indicator",
1972 UiSize::new(500.0, 168.0),
1973 );
1974 push_window(
1975 &mut windows,
1976 state.windows.lists_tables,
1977 "lists_tables",
1978 "Lists and tables",
1979 UiSize::new(wide, 620.0),
1980 );
1981 push_window(
1982 &mut windows,
1983 state.windows.property_inspector,
1984 "property_inspector",
1985 "Property inspector",
1986 UiSize::new(330.0, 250.0),
1987 );
1988 push_window(
1989 &mut windows,
1990 state.windows.trees,
1991 "trees",
1992 "Trees",
1993 UiSize::new(430.0, 390.0),
1994 );
1995 push_window(
1996 &mut windows,
1997 state.windows.layout_widgets,
1998 "layout_widgets",
1999 "Layout widgets",
2000 UiSize::new(wide.min(560.0), 400.0),
2001 );
2002 push_window(
2003 &mut windows,
2004 state.windows.containers,
2005 "containers",
2006 "Containers",
2007 UiSize::new(560.0, 640.0),
2008 );
2009 push_window(
2010 &mut windows,
2011 state.windows.forms,
2012 "forms",
2013 "Forms",
2014 UiSize::new(460.0, 520.0),
2015 );
2016 push_window(
2017 &mut windows,
2018 state.windows.overlays,
2019 "overlays",
2020 "Overlays",
2021 UiSize::new(560.0, 500.0),
2022 );
2023 push_window(
2024 &mut windows,
2025 state.windows.drag_drop,
2026 "drag_drop",
2027 "Drag and drop",
2028 UiSize::new(390.0, 340.0),
2029 );
2030 push_window(
2031 &mut windows,
2032 state.windows.media,
2033 "media",
2034 "Media",
2035 UiSize::new(360.0, 280.0),
2036 );
2037 push_window(
2038 &mut windows,
2039 state.windows.timeline,
2040 "timeline",
2041 "Timeline",
2042 UiSize::new(600.0, 120.0),
2043 );
2044 push_window(
2045 &mut windows,
2046 state.windows.toasts,
2047 "toasts",
2048 "Toasts",
2049 UiSize::new(320.0, 270.0),
2050 );
2051 push_window(
2052 &mut windows,
2053 state.windows.popup_panel,
2054 "popup_panel",
2055 "Popup panel",
2056 UiSize::new(360.0, 200.0),
2057 );
2058 push_window(
2059 &mut windows,
2060 state.windows.canvas,
2061 "canvas",
2062 "Canvas",
2063 UiSize::new(420.0, 292.0),
2064 );
2065 push_window(
2066 &mut windows,
2067 state.windows.styling,
2068 "styling",
2069 "Styling",
2070 UiSize::new(640.0, 560.0),
2071 );
2072 for window in &mut windows {
2073 window.drag_action = Some(WidgetActionBinding::action(format!(
2074 "window.drag.{}",
2075 window.id
2076 )));
2077 window.collapse_action = Some(WidgetActionBinding::action(format!(
2078 "window.collapse.{}",
2079 window.id
2080 )));
2081 window.resize_action = Some(WidgetActionBinding::action(format!(
2082 "window.resize.{}",
2083 window.id
2084 )));
2085 state
2086 .desktop
2087 .apply_to_descriptor(window, window_defaults(window.id.as_str()));
2088 }
2089 windows
2090}
2091
2092fn push_window(
2093 windows: &mut Vec<widgets::FloatingWindowDescriptor>,
2094 visible: bool,
2095 id: &'static str,
2096 title: &'static str,
2097 preferred_size: UiSize,
2098) {
2099 if visible {
2100 windows.push(
2101 widgets::FloatingWindowDescriptor::new(id, title, preferred_size)
2102 .with_min_size(default_window_state_min_size(id))
2103 .with_auto_size_to_content(true)
2104 .with_activate_action(format!("window.activate.{id}"))
2105 .with_close_action(format!("window.close.{id}")),
2106 );
2107 }
2108}
2109
2110fn default_window_size(id: &str) -> UiSize {
2111 match id {
2112 "labels" => UiSize::new(380.0, 460.0),
2113 "buttons" => UiSize::new(604.0, 220.0),
2114 "checkbox" => UiSize::new(250.0, 72.0),
2115 "toggles" => UiSize::new(360.0, 380.0),
2116 "slider" => UiSize::new(430.0, 560.0),
2117 "numeric" => UiSize::new(430.0, 180.0),
2118 "text_input" => UiSize::new(520.0, 640.0),
2119 "selection" => UiSize::new(360.0, 360.0),
2120 "menus" => UiSize::new(640.0, 640.0),
2121 "command_palette" => UiSize::new(520.0, 320.0),
2122 "date_picker" => UiSize::new(430.0, 390.0),
2123 "color_picker" => UiSize::new(340.0, 390.0),
2124 "color_buttons" => UiSize::new(430.0, 360.0),
2125 "progress" => UiSize::new(500.0, 168.0),
2126 "lists_tables" => UiSize::new(600.0, 700.0),
2127 "property_inspector" => UiSize::new(330.0, 250.0),
2128 "trees" => UiSize::new(430.0, 450.0),
2129 "layout_widgets" => UiSize::new(560.0, 400.0),
2130 "containers" => UiSize::new(560.0, 640.0),
2131 "forms" => UiSize::new(460.0, 520.0),
2132 "overlays" => UiSize::new(560.0, 500.0),
2133 "drag_drop" => UiSize::new(390.0, 340.0),
2134 "media" => UiSize::new(360.0, 280.0),
2135 "timeline" => UiSize::new(600.0, 120.0),
2136 "toasts" => UiSize::new(320.0, 270.0),
2137 "popup_panel" => UiSize::new(360.0, 200.0),
2138 "canvas" => UiSize::new(420.0, 292.0),
2139 "styling" => UiSize::new(640.0, 560.0),
2140 _ => UiSize::new(300.0, 180.0),
2141 }
2142}
2143
2144fn default_window_state_min_size(_id: &str) -> UiSize {
2145 UiSize::new(160.0, 96.0)
2146}
2147
2148fn default_window_position(id: &str) -> UiPoint {
2149 match id {
2150 "labels" => UiPoint::new(18.0, 18.0),
2151 "buttons" => UiPoint::new(420.0, 18.0),
2152 "checkbox" => UiPoint::new(360.0, 18.0),
2153 "toggles" => UiPoint::new(360.0, 110.0),
2154 "slider" => UiPoint::new(360.0, 110.0),
2155 "numeric" => UiPoint::new(360.0, 260.0),
2156 "text_input" => UiPoint::new(360.0, 18.0),
2157 "selection" => UiPoint::new(360.0, 404.0),
2158 "menus" => UiPoint::new(18.0, 18.0),
2159 "command_palette" => UiPoint::new(68.0, 88.0),
2160 "date_picker" => UiPoint::new(300.0, 170.0),
2161 "color_picker" => UiPoint::new(18.0, 560.0),
2162 "color_buttons" => UiPoint::new(380.0, 500.0),
2163 "progress" => UiPoint::new(72.0, 540.0),
2164 "lists_tables" => UiPoint::new(18.0, 90.0),
2165 "property_inspector" => UiPoint::new(300.0, 420.0),
2166 "trees" => UiPoint::new(36.0, 220.0),
2167 "layout_widgets" => UiPoint::new(18.0, 18.0),
2168 "containers" => UiPoint::new(48.0, 120.0),
2169 "forms" => UiPoint::new(120.0, 160.0),
2170 "overlays" => UiPoint::new(80.0, 110.0),
2171 "drag_drop" => UiPoint::new(210.0, 250.0),
2172 "media" => UiPoint::new(120.0, 360.0),
2173 "timeline" => UiPoint::new(18.0, 620.0),
2174 "toasts" => UiPoint::new(320.0, 70.0),
2175 "popup_panel" => UiPoint::new(320.0, 370.0),
2176 "canvas" => UiPoint::new(380.0, 560.0),
2177 "styling" => UiPoint::new(86.0, 118.0),
2178 _ => UiPoint::new(18.0, 18.0),
2179 }
2180}
2181
2182fn window_for_action(action_id: &str) -> Option<&'static str> {
2183 match action_id {
2184 id if id.starts_with("labels.") => Some("labels"),
2185 id if id.starts_with("button.") => Some("buttons"),
2186 id if id.starts_with("checkbox.") => Some("checkbox"),
2187 id if id.starts_with("toggles.") => Some("toggles"),
2188 id if id.starts_with("theme.preference.") => Some("toggles"),
2189 id if id.starts_with("slider.") => Some("slider"),
2190 id if id.starts_with("numeric.") => Some("numeric"),
2191 id if id.starts_with("text.") => Some("text_input"),
2192 id if id.starts_with("combo.")
2193 || id.starts_with("selection.dropdown.")
2194 || id.starts_with("selection.menu.") =>
2195 {
2196 Some("selection")
2197 }
2198 id if id.starts_with("menus.") => Some("menus"),
2199 id if id.starts_with("command_palette.") => Some("command_palette"),
2200 id if id.starts_with("date.") => Some("date_picker"),
2201 id if id.starts_with("color.") => Some("color_picker"),
2202 id if id.starts_with("color_buttons.") => Some("color_buttons"),
2203 id if id.starts_with("progress.") => Some("progress"),
2204 id if id.starts_with("lists_tables.") => Some("lists_tables"),
2205 id if id.starts_with("property_inspector.") => Some("property_inspector"),
2206 id if id.starts_with("trees.") => Some("trees"),
2207 id if id.starts_with("layout.") || id.starts_with("layout_widgets.") => {
2208 Some("layout_widgets")
2209 }
2210 id if id.starts_with("containers.") => Some("containers"),
2211 id if id.starts_with("forms.") => Some("forms"),
2212 id if id.starts_with("overlays.") => Some("overlays"),
2213 id if id.starts_with("drag_drop.") => Some("drag_drop"),
2214 id if id.starts_with("media.") => Some("media"),
2215 id if id.starts_with("toast.") => Some("toasts"),
2216 id if id.starts_with("popup.") => Some("popup_panel"),
2217 id if id.starts_with("canvas.") => Some("canvas"),
2218 id if id.starts_with("styling.") => Some("styling"),
2219 _ => None,
2220 }
2221}
2222
2223fn focused_text_for_action(action_id: &str) -> Option<FocusedTextInput> {
2224 Some(match action_id {
2225 "text.input.edit" => FocusedTextInput::Editable,
2226 "text.selectable.edit" => FocusedTextInput::Selectable,
2227 "text.singleline.edit" => FocusedTextInput::Singleline,
2228 "text.multiline.edit" => FocusedTextInput::Multiline,
2229 "text.area.edit" => FocusedTextInput::TextArea,
2230 "text.code_editor.edit" => FocusedTextInput::CodeEditor,
2231 "text.search.edit" => FocusedTextInput::Search,
2232 "text.password.edit" => FocusedTextInput::Password,
2233 "text.selectable_helper.edit" => FocusedTextInput::SelectableHelper,
2234 "slider.value_text.edit" => FocusedTextInput::SliderValue,
2235 "slider.left_text.edit" => FocusedTextInput::SliderRangeLeft,
2236 "slider.right_text.edit" => FocusedTextInput::SliderRangeRight,
2237 "slider.step_text.edit" => FocusedTextInput::SliderStep,
2238 _ => return None,
2239 })
2240}
2241
2242fn control_panel(
2243 ui: &mut UiDocument,
2244 parent: UiNodeId,
2245 state: &ShowcaseState,
2246 viewport_height: f32,
2247) {
2248 widgets::label(
2249 ui,
2250 parent,
2251 "controls.title",
2252 "Widgets",
2253 text(16.0, color(244, 248, 252)),
2254 LayoutStyle::new().with_width_percent(1.0),
2255 );
2256 let list_viewport_height = controls_list_viewport_height(viewport_height);
2257 let list_row = ui.add_child(
2258 parent,
2259 UiNode::container(
2260 "controls.widget_list.row",
2261 LayoutStyle::row()
2262 .with_width_percent(1.0)
2263 .with_height(list_viewport_height)
2264 .with_flex_grow(1.0)
2265 .with_flex_shrink(1.0)
2266 .gap(6.0),
2267 ),
2268 );
2269 let list = widgets::scroll_area(
2270 ui,
2271 list_row,
2272 "controls.widget_list",
2273 ScrollAxes::VERTICAL,
2274 LayoutStyle::column()
2275 .with_width(0.0)
2276 .with_height_percent(1.0)
2277 .with_flex_grow(1.0)
2278 .gap(CONTROLS_WIDGET_ROW_GAP),
2279 );
2280 ui.node_mut(list).action = Some("controls.widget_list.scroll".into());
2281 if let Some(scroll) = ui.node_mut(list).scroll.as_mut() {
2282 scroll.offset.y = controls_scroll_state(state.controls_scroll, list_viewport_height)
2283 .offset
2284 .y;
2285 }
2286 widgets::scrollbar(
2287 ui,
2288 list_row,
2289 "controls.widget_list.scrollbar",
2290 controls_scroll_state(state.controls_scroll, list_viewport_height),
2291 widgets::ScrollAxis::Vertical,
2292 widgets::ScrollbarOptions::default()
2293 .with_layout(LayoutStyle::new().with_width(8.0).with_height_percent(1.0))
2294 .with_track_size(UiSize::new(8.0, list_viewport_height))
2295 .with_action("controls.widget_list.scrollbar"),
2296 );
2297
2298 window_toggle(ui, list, "labels", "Labels", state.windows.labels);
2299 window_toggle(ui, list, "buttons", "Buttons", state.windows.buttons);
2300 window_toggle(ui, list, "checkbox", "Checkbox", state.windows.checkbox);
2301 window_toggle(
2302 ui,
2303 list,
2304 "toggles",
2305 "Radio and toggles",
2306 state.windows.toggles,
2307 );
2308 window_toggle(ui, list, "slider", "Slider", state.windows.slider);
2309 window_toggle(ui, list, "numeric", "Numeric input", state.windows.numeric);
2310 window_toggle(
2311 ui,
2312 list,
2313 "text_input",
2314 "Text input",
2315 state.windows.text_input,
2316 );
2317 window_toggle(
2318 ui,
2319 list,
2320 "selection",
2321 "Select controls",
2322 state.windows.selection,
2323 );
2324 window_toggle(ui, list, "menus", "Menus", state.windows.menus);
2325 window_toggle(
2326 ui,
2327 list,
2328 "command_palette",
2329 "Command palette",
2330 state.windows.command_palette,
2331 );
2332 window_toggle(
2333 ui,
2334 list,
2335 "date_picker",
2336 "Date picker",
2337 state.windows.date_picker,
2338 );
2339 window_toggle(
2340 ui,
2341 list,
2342 "color_picker",
2343 "Color picker",
2344 state.windows.color_picker,
2345 );
2346 window_toggle(
2347 ui,
2348 list,
2349 "color_buttons",
2350 "Color buttons",
2351 state.windows.color_buttons,
2352 );
2353 window_toggle(
2354 ui,
2355 list,
2356 "progress",
2357 "Progress indicator",
2358 state.windows.progress,
2359 );
2360 window_toggle(
2361 ui,
2362 list,
2363 "lists_tables",
2364 "Lists and tables",
2365 state.windows.lists_tables,
2366 );
2367 window_toggle(
2368 ui,
2369 list,
2370 "property_inspector",
2371 "Property inspector",
2372 state.windows.property_inspector,
2373 );
2374 window_toggle(ui, list, "trees", "Trees", state.windows.trees);
2375 window_toggle(
2376 ui,
2377 list,
2378 "layout_widgets",
2379 "Layout widgets",
2380 state.windows.layout_widgets,
2381 );
2382 window_toggle(
2383 ui,
2384 list,
2385 "containers",
2386 "Containers",
2387 state.windows.containers,
2388 );
2389 window_toggle(ui, list, "forms", "Forms", state.windows.forms);
2390 window_toggle(ui, list, "overlays", "Overlays", state.windows.overlays);
2391 window_toggle(
2392 ui,
2393 list,
2394 "drag_drop",
2395 "Drag and drop",
2396 state.windows.drag_drop,
2397 );
2398 window_toggle(ui, list, "media", "Media", state.windows.media);
2399 window_toggle(ui, list, "timeline", "Timeline", state.windows.timeline);
2400 window_toggle(ui, list, "toasts", "Toasts", state.windows.toasts);
2401 window_toggle(
2402 ui,
2403 list,
2404 "popup_panel",
2405 "Popup panel",
2406 state.windows.popup_panel,
2407 );
2408 window_toggle(ui, list, "canvas", "Canvas", state.windows.canvas);
2409 window_toggle(ui, list, "styling", "Styling", state.windows.styling);
2410
2411 ui.add_child(
2412 parent,
2413 UiNode::container(
2414 "controls.clear_all.spacer",
2415 LayoutStyle::new()
2416 .with_width_percent(1.0)
2417 .with_height(1.0)
2418 .with_flex_grow(1.0)
2419 .with_flex_shrink(1.0),
2420 ),
2421 );
2422 let mut clear =
2423 widgets::ButtonOptions::new(LayoutStyle::new().with_width_percent(1.0).with_height(30.0))
2424 .with_action("window.clear_all");
2425 clear.visual = UiVisual::panel(
2426 color(31, 38, 48),
2427 Some(StrokeStyle::new(color(76, 88, 106), 1.0)),
2428 4.0,
2429 );
2430 clear.hovered_visual = Some(UiVisual::panel(
2431 color(45, 56, 70),
2432 Some(StrokeStyle::new(color(118, 144, 174), 1.0)),
2433 4.0,
2434 ));
2435 clear.pressed_visual = Some(UiVisual::panel(
2436 color(20, 27, 36),
2437 Some(StrokeStyle::new(color(82, 104, 132), 1.0)),
2438 4.0,
2439 ));
2440 clear.pressed_hovered_visual = Some(UiVisual::panel(
2441 color(36, 48, 62),
2442 Some(StrokeStyle::new(color(138, 170, 206), 1.0)),
2443 4.0,
2444 ));
2445 clear.text_style = text(12.0, color(230, 236, 246));
2446 clear.accessibility_label = Some("Clear all widgets".to_string());
2447 widgets::button(ui, parent, "controls.clear_all", "Clear all", clear);
2448}
2449
2450fn window_toggle(
2451 ui: &mut UiDocument,
2452 parent: UiNodeId,
2453 id: &'static str,
2454 label: &'static str,
2455 checked: bool,
2456) {
2457 let mut options =
2458 widgets::CheckboxOptions::default().with_action(format!("window.toggle.{id}"));
2459 options.layout = LayoutStyle::new()
2460 .with_width_percent(1.0)
2461 .with_height(CONTROLS_WIDGET_ROW_HEIGHT);
2462 options.text_style = text(12.0, color(220, 228, 238));
2463 widgets::checkbox(
2464 ui,
2465 parent,
2466 format!("controls.{id}"),
2467 label,
2468 checked,
2469 options,
2470 );
2471}
2472
2473fn labels(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
2474 let body = section(ui, parent, "labels", "Labels");
2475 ui.node_mut(body).style.layout = LayoutStyle::column()
2476 .with_width_percent(1.0)
2477 .with_height_percent(1.0)
2478 .with_flex_grow(1.0)
2479 .gap(10.0)
2480 .as_taffy_style()
2481 .clone();
2482 widgets::label(
2483 ui,
2484 body,
2485 "labels.plain",
2486 "Plain label",
2487 text(13.0, color(226, 232, 242)),
2488 LayoutStyle::new().with_width_percent(1.0),
2489 );
2490 let locale_items = label_locale_options();
2491 let locale_id = state
2492 .label_locale
2493 .selected_id(&locale_items)
2494 .unwrap_or("es-MX");
2495 let localization = LocalizationPolicy::new(LocaleId::new(locale_id).expect("valid locale"));
2496 let locale_row = ui.add_child(
2497 body,
2498 UiNode::container(
2499 "labels.locale.row",
2500 LayoutStyle::row()
2501 .with_width_percent(1.0)
2502 .with_align_items(taffy::prelude::AlignItems::Center)
2503 .gap(10.0),
2504 ),
2505 );
2506 let locale_label_width = 270.0;
2507 let locale_dropdown_width = 148.0;
2508 let locale_gap = 10.0;
2509 widgets::localized_label(
2510 ui,
2511 locale_row,
2512 "labels.localized",
2513 DynamicLabelMeta::keyed("showcase.localized.greeting", localized_label(locale_id)),
2514 Some(&localization),
2515 text(13.0, color(170, 202, 255)),
2516 LayoutStyle::new().with_width(locale_label_width),
2517 );
2518 let mut locale_options = widgets::DropdownSelectOptions::default();
2519 locale_options.trigger_layout = LayoutStyle::row()
2520 .with_width(locale_dropdown_width)
2521 .with_height(30.0)
2522 .with_align_items(taffy::prelude::AlignItems::Center)
2523 .with_justify_content(taffy::prelude::JustifyContent::Center)
2524 .padding(6.0);
2525 locale_options.text_style = text(13.0, color(226, 232, 242));
2526 locale_options.accessibility_label = Some("Locale".to_string());
2527 locale_options.menu = widgets::SelectMenuOptions::default().with_action_prefix("labels.locale");
2528 locale_options.menu.width = locale_dropdown_width;
2529 locale_options.menu.row_height = 30.0;
2530 locale_options.menu.max_visible_rows = locale_items.len();
2531 locale_options.menu.text_style = text(13.0, color(226, 232, 242));
2532 let locale_nodes = widgets::dropdown_select(
2533 ui,
2534 locale_row,
2535 "labels.locale",
2536 &locale_items,
2537 &state.label_locale,
2538 Some(widgets::AnchoredPopup::new(
2539 UiRect::new(
2540 locale_label_width + locale_gap,
2541 0.0,
2542 locale_dropdown_width,
2543 30.0,
2544 ),
2545 UiRect::new(0.0, 0.0, 460.0, 260.0),
2546 widgets::PopupPlacement::default(),
2547 )),
2548 locale_options,
2549 );
2550 ui.node_mut(locale_nodes.trigger).action = Some("labels.locale.toggle".into());
2551 widgets::label(
2552 ui,
2553 body,
2554 "labels.muted",
2555 "Muted helper label",
2556 text(12.0, color(154, 166, 184)),
2557 LayoutStyle::new().with_width_percent(1.0),
2558 );
2559
2560 let sizes = ui.add_child(
2561 body,
2562 UiNode::container(
2563 "labels.sizes",
2564 LayoutStyle::row()
2565 .with_width_percent(1.0)
2566 .with_align_items(taffy::prelude::AlignItems::FlexEnd)
2567 .gap(12.0),
2568 ),
2569 );
2570 widgets::label(
2571 ui,
2572 sizes,
2573 "labels.size.small",
2574 "12px",
2575 text(12.0, color(226, 232, 242)),
2576 LayoutStyle::new(),
2577 );
2578 widgets::label(
2579 ui,
2580 sizes,
2581 "labels.size.default",
2582 "13px",
2583 text(13.0, color(226, 232, 242)),
2584 LayoutStyle::new(),
2585 );
2586 widgets::label(
2587 ui,
2588 sizes,
2589 "labels.size.large",
2590 "18px",
2591 text(18.0, color(246, 249, 252)),
2592 LayoutStyle::new(),
2593 );
2594 widgets::label(
2595 ui,
2596 sizes,
2597 "labels.size.display",
2598 "24px",
2599 text(24.0, color(246, 249, 252)),
2600 LayoutStyle::new(),
2601 );
2602
2603 let style_row = row(ui, body, "labels.styles", 12.0);
2604 let mut bold = text(13.0, color(246, 249, 252));
2605 bold.weight = FontWeight::BOLD;
2606 widgets::label(
2607 ui,
2608 style_row,
2609 "labels.style.bold",
2610 "Bold",
2611 bold,
2612 LayoutStyle::new(),
2613 );
2614 widgets::label(
2615 ui,
2616 style_row,
2617 "labels.style.weak",
2618 "Muted",
2619 text(13.0, color(154, 166, 184)),
2620 LayoutStyle::new(),
2621 );
2622
2623 let font_row = row(ui, body, "labels.fonts", 12.0);
2624 let mut serif = text(13.0, color(226, 232, 242));
2625 serif.family = FontFamily::Serif;
2626 widgets::label(
2627 ui,
2628 font_row,
2629 "labels.font.serif",
2630 "Serif",
2631 serif,
2632 LayoutStyle::new(),
2633 );
2634 let mut mono = text(13.0, color(226, 232, 242));
2635 mono.family = FontFamily::Monospace;
2636 widgets::label(
2637 ui,
2638 font_row,
2639 "labels.font.mono",
2640 "Monospace",
2641 mono,
2642 LayoutStyle::new(),
2643 );
2644
2645 let code_panel = ui.add_child(
2646 body,
2647 UiNode::container(
2648 "labels.code.panel",
2649 LayoutStyle::new()
2650 .with_width_percent(1.0)
2651 .padding(8.0)
2652 .with_height(36.0),
2653 )
2654 .with_visual(UiVisual::panel(
2655 color(10, 14, 20),
2656 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
2657 4.0,
2658 )),
2659 );
2660 widgets::code_label(
2661 ui,
2662 code_panel,
2663 "labels.code",
2664 "let label = widgets::label(...);",
2665 LayoutStyle::new().with_width_percent(1.0),
2666 );
2667
2668 let colors = row(ui, body, "labels.colors", 14.0);
2669 widgets::colored_label(
2670 ui,
2671 colors,
2672 "labels.color.green",
2673 "Green",
2674 color(111, 203, 159),
2675 LayoutStyle::new(),
2676 );
2677 widgets::colored_label(
2678 ui,
2679 colors,
2680 "labels.color.yellow",
2681 "Yellow",
2682 color(232, 196, 101),
2683 LayoutStyle::new(),
2684 );
2685 widgets::colored_label(
2686 ui,
2687 colors,
2688 "labels.color.red",
2689 "Red",
2690 color(244, 118, 118),
2691 LayoutStyle::new(),
2692 );
2693
2694 let wrap_row = wrapping_row(ui, body, "labels.wrap.row", 10.0);
2695 let wrap_word = ui.add_child(
2696 wrap_row,
2697 UiNode::container(
2698 "labels.wrap.word.panel",
2699 LayoutStyle::column().with_width(172.0).padding(8.0),
2700 )
2701 .with_visual(UiVisual::panel(
2702 color(18, 23, 31),
2703 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
2704 4.0,
2705 )),
2706 );
2707 widgets::wrapped_label(
2708 ui,
2709 wrap_word,
2710 "labels.wrap.word",
2711 "Word wrapping keeps this sentence readable in a narrow box.",
2712 TextWrap::Word,
2713 LayoutStyle::new().with_width_percent(1.0),
2714 );
2715 let wrap_glyph = ui.add_child(
2716 wrap_row,
2717 UiNode::container(
2718 "labels.wrap.glyph.panel",
2719 LayoutStyle::column().with_width(172.0).padding(8.0),
2720 )
2721 .with_visual(UiVisual::panel(
2722 color(18, 23, 31),
2723 Some(StrokeStyle::new(color(47, 59, 74), 1.0)),
2724 4.0,
2725 )),
2726 );
2727 widgets::wrapped_label(
2728 ui,
2729 wrap_glyph,
2730 "labels.wrap.glyph",
2731 "LongIdentifierWithoutSpaces",
2732 TextWrap::Glyph,
2733 LayoutStyle::new().with_width_percent(1.0),
2734 );
2735
2736 let links = wrapping_row(ui, body, "labels.links", 12.0);
2737 widgets::link(
2738 ui,
2739 links,
2740 "labels.link",
2741 "Internal action",
2742 widgets::LinkOptions::default()
2743 .visited(state.label_link_visited)
2744 .with_action("labels.link"),
2745 );
2746 widgets::hyperlink(
2747 ui,
2748 links,
2749 "labels.hyperlink",
2750 "Open docs.rs",
2751 "https://docs.rs/operad",
2752 widgets::LinkOptions::default()
2753 .visited(state.label_hyperlink_visited)
2754 .with_action("labels.hyperlink"),
2755 );
2756 if state.label_link_status != "No link action yet" {
2757 widgets::label(
2758 ui,
2759 body,
2760 "labels.status",
2761 format!("Last action: {}", state.label_link_status),
2762 text(12.0, color(154, 166, 184)),
2763 LayoutStyle::new().with_width_percent(1.0),
2764 );
2765 }
2766}
2767
2768fn buttons(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
2769 let body = section(ui, parent, "buttons", "Buttons");
2770 let primary_row = wrapping_row(ui, body, "buttons.row", 10.0);
2771 button(
2772 ui,
2773 primary_row,
2774 "button.default",
2775 "Default",
2776 "button.default",
2777 button_visual(38, 46, 58),
2778 );
2779 button(
2780 ui,
2781 primary_row,
2782 "button.primary",
2783 "Primary",
2784 "button.primary",
2785 button_visual(48, 112, 184),
2786 );
2787 button(
2788 ui,
2789 primary_row,
2790 "button.secondary",
2791 "Secondary",
2792 "button.secondary",
2793 button_visual(58, 78, 96),
2794 );
2795 button(
2796 ui,
2797 primary_row,
2798 "button.destructive",
2799 "Destructive",
2800 "button.destructive",
2801 button_visual(157, 65, 73),
2802 );
2803 let mut disabled = widgets::ButtonOptions::new(LayoutStyle::size(92.0, 32.0));
2804 disabled.enabled = false;
2805 disabled.visual = button_visual(40, 44, 52);
2806 disabled.text_style = text(13.0, color(138, 146, 158));
2807 widgets::button(ui, primary_row, "button.disabled", "Disabled", disabled);
2808 let second_row = wrapping_row(ui, body, "buttons.row.options", 10.0);
2809 button(
2810 ui,
2811 second_row,
2812 "button.momentary",
2813 "Press only",
2814 "button.default",
2815 button_visual(42, 50, 62),
2816 );
2817 let mut toggle =
2818 widgets::ButtonOptions::new(LayoutStyle::size(112.0, 32.0)).with_action("button.toggle");
2819 toggle.pressed = state.toggle_button;
2820 toggle.visual = button_visual(42, 50, 62);
2821 toggle.hovered_visual = Some(button_visual(62, 74, 92));
2822 toggle.pressed_visual = Some(button_visual(86, 64, 156));
2823 toggle.pressed_hovered_visual = Some(button_visual(126, 94, 218));
2824 toggle.text_style = text(13.0, color(246, 249, 252));
2825 widgets::button(
2826 ui,
2827 second_row,
2828 "button.toggle",
2829 if state.toggle_button {
2830 "Toggle on"
2831 } else {
2832 "Toggle off"
2833 },
2834 toggle,
2835 );
2836 let mut forced_pressed = widgets::ButtonOptions::new(LayoutStyle::size(112.0, 32.0));
2837 forced_pressed.pressed = true;
2838 forced_pressed.visual = button_visual(42, 50, 62);
2839 forced_pressed.hovered_visual = Some(button_visual(62, 74, 92));
2840 forced_pressed.pressed_visual = Some(button_visual(38, 82, 136));
2841 forced_pressed.pressed_hovered_visual = Some(button_visual(62, 126, 196));
2842 forced_pressed.text_style = text(13.0, color(246, 249, 252));
2843 widgets::button(
2844 ui,
2845 second_row,
2846 "button.state.pressed",
2847 "Pressed",
2848 forced_pressed,
2849 );
2850 let helper_row = wrapping_row(ui, body, "buttons.row.helpers", 10.0);
2851 widgets::small_button(
2852 ui,
2853 helper_row,
2854 "button.small",
2855 "Small",
2856 widgets::ButtonOptions::default().with_action("button.small"),
2857 );
2858 widgets::icon_button(
2859 ui,
2860 helper_row,
2861 "button.icon",
2862 icon_image(BuiltInIcon::Settings),
2863 "Settings",
2864 widgets::ButtonOptions::default().with_action("button.icon"),
2865 );
2866 widgets::image_button(
2867 ui,
2868 helper_row,
2869 "button.image",
2870 icon_image(BuiltInIcon::Folder),
2871 "Folder",
2872 widgets::ButtonOptions::default().with_action("button.image"),
2873 );
2874 widgets::reset_button(
2875 ui,
2876 helper_row,
2877 "button.reset",
2878 state.toggle_button,
2879 widgets::ButtonOptions::default().with_action("button.reset"),
2880 );
2881 widgets::toggle_button(
2882 ui,
2883 helper_row,
2884 "button.toggle_helper",
2885 "Toggle helper",
2886 state.toggle_button,
2887 widgets::ButtonOptions::default().with_action("button.toggle"),
2888 );
2889 widgets::label(
2890 ui,
2891 body,
2892 "buttons.last",
2893 format!("Last pressed: {}", state.last_button),
2894 text(12.0, color(154, 166, 184)),
2895 LayoutStyle::new().with_width_percent(1.0),
2896 );
2897}
2898
2899fn checkbox(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
2900 let body = section(ui, parent, "checkbox", "Checkbox");
2901 let mut options = widgets::CheckboxOptions::default().with_action("checkbox.enabled");
2902 options.text_style = text(13.0, color(222, 228, 238));
2903 widgets::checkbox(
2904 ui,
2905 body,
2906 "checkbox.enabled",
2907 if state.checked { "Enabled" } else { "Disabled" },
2908 state.checked,
2909 options,
2910 );
2911}
2912
2913fn toggles(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
2914 let body = section(ui, parent, "toggles", "Radio and toggles");
2915 let radio_options = [
2916 widgets::RadioOption::new("compact", "Compact").with_action("toggles.radio.compact"),
2917 widgets::RadioOption::new("comfortable", "Comfortable")
2918 .with_action("toggles.radio.comfortable"),
2919 widgets::RadioOption::new("spacious", "Spacious").with_action("toggles.radio.spacious"),
2920 widgets::RadioOption::new("disabled", "Disabled").enabled(false),
2921 ];
2922 widgets::radio_group(
2923 ui,
2924 body,
2925 "toggles.radio_group",
2926 &radio_options,
2927 Some(state.radio_choice),
2928 widgets::RadioGroupOptions::default(),
2929 );
2930 widgets::radio_button(
2931 ui,
2932 body,
2933 "toggles.radio_single",
2934 "Standalone radio button",
2935 true,
2936 widgets::RadioButtonOptions::default().with_action("toggles.radio.compact"),
2937 );
2938 widgets::toggle_switch(
2939 ui,
2940 body,
2941 "toggles.switch",
2942 if state.switch_enabled {
2943 "Switch on"
2944 } else {
2945 "Switch off"
2946 },
2947 widgets::ToggleValue::from(state.switch_enabled),
2948 widgets::ToggleSwitchOptions::default().with_action("toggles.switch"),
2949 );
2950 widgets::toggle_switch(
2951 ui,
2952 body,
2953 "toggles.mixed",
2954 match state.mixed_switch {
2955 widgets::ToggleValue::Mixed => "Mixed switch",
2956 widgets::ToggleValue::On => "Mixed switch on",
2957 widgets::ToggleValue::Off => "Mixed switch off",
2958 },
2959 state.mixed_switch,
2960 widgets::ToggleSwitchOptions::default().with_action("toggles.mixed"),
2961 );
2962 widgets::theme_preference_buttons(
2963 ui,
2964 body,
2965 "toggles.theme_buttons",
2966 state.theme_preference,
2967 widgets::ThemePreferenceButtonsOptions::default().with_action_prefix("toggles.theme"),
2968 );
2969 widgets::theme_preference_switch(
2970 ui,
2971 body,
2972 "toggles.theme_switch",
2973 state.theme_preference,
2974 widgets::ThemePreferenceSwitchOptions::default().with_action("theme.preference.dark"),
2975 );
2976}
2977
2978fn slider(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
2979 let body = section(ui, parent, "slider", "Slider");
2980 widgets::label(
2981 ui,
2982 body,
2983 "slider.note",
2984 "Click a slider value to edit it with the keyboard.",
2985 text(12.0, color(166, 176, 190)),
2986 LayoutStyle::new().with_width_percent(1.0),
2987 );
2988
2989 let value_row = row(ui, body, "slider.value.row", 10.0);
2990 let options = slider_options(state, 180.0).with_value_edit_action("slider.value");
2991 let slider_unit = state.slider_value_spec().normalize(state.slider);
2992 widgets::slider(
2993 ui,
2994 value_row,
2995 "slider.value",
2996 slider_unit,
2997 0.0..1.0,
2998 options.clone(),
2999 );
3000 slider_number_input(
3001 ui,
3002 value_row,
3003 "slider.value_text",
3004 &state.slider_value_text,
3005 FocusedTextInput::SliderValue,
3006 state,
3007 86.0,
3008 );
3009 widgets::label(
3010 ui,
3011 value_row,
3012 "slider.value.label",
3013 "f64 demo slider",
3014 text(12.0, color(186, 198, 216)),
3015 LayoutStyle::new().with_width_percent(1.0),
3016 );
3017
3018 widgets::label(
3019 ui,
3020 body,
3021 "slider.precision",
3022 format!(
3023 "Displayed value: {} Full precision: {:.6}",
3024 widgets::format_slider_value(state.slider),
3025 state.slider
3026 ),
3027 text(11.0, color(154, 166, 184)),
3028 LayoutStyle::new().with_width_percent(1.0),
3029 );
3030
3031 divider(ui, body, "slider.divider.range");
3032 widgets::label(
3033 ui,
3034 body,
3035 "slider.range.label",
3036 "Slider range",
3037 text(12.0, color(220, 228, 238)),
3038 LayoutStyle::new().with_width_percent(1.0),
3039 );
3040 let left_row = row(ui, body, "slider.range.left.row", 10.0);
3041 let left_options = widgets::SliderOptions::default()
3042 .with_layout(
3043 LayoutStyle::new()
3044 .with_width(180.0)
3045 .with_height(24.0)
3046 .with_flex_shrink(0.0),
3047 )
3048 .with_value_edit_action("slider.range_left");
3049 widgets::slider(
3050 ui,
3051 left_row,
3052 "slider.range_left",
3053 state.slider_left,
3054 0.0..state.slider_right.max(1.0),
3055 left_options,
3056 );
3057 slider_number_input(
3058 ui,
3059 left_row,
3060 "slider.left_text",
3061 &state.slider_left_text,
3062 FocusedTextInput::SliderRangeLeft,
3063 state,
3064 96.0,
3065 );
3066 widgets::label(
3067 ui,
3068 left_row,
3069 "slider.range.left.label",
3070 "left",
3071 text(12.0, color(186, 198, 216)),
3072 LayoutStyle::new().with_width(46.0),
3073 );
3074 let right_row = row(ui, body, "slider.range.right.row", 10.0);
3075 let right_options = widgets::SliderOptions::default()
3076 .with_layout(
3077 LayoutStyle::new()
3078 .with_width(180.0)
3079 .with_height(24.0)
3080 .with_flex_shrink(0.0),
3081 )
3082 .with_value_edit_action("slider.range_right");
3083 widgets::slider(
3084 ui,
3085 right_row,
3086 "slider.range_right",
3087 state.slider_right,
3088 (state.slider_left + 1.0)..10000.0,
3089 right_options,
3090 );
3091 slider_number_input(
3092 ui,
3093 right_row,
3094 "slider.right_text",
3095 &state.slider_right_text,
3096 FocusedTextInput::SliderRangeRight,
3097 state,
3098 96.0,
3099 );
3100 widgets::label(
3101 ui,
3102 right_row,
3103 "slider.range.right.label",
3104 "right",
3105 text(12.0, color(186, 198, 216)),
3106 LayoutStyle::new().with_width(46.0),
3107 );
3108
3109 divider(ui, body, "slider.divider.trailing");
3110 let trailing_row = row(ui, body, "slider.trailing.row", 8.0);
3111 slider_checkbox_with_layout(
3112 ui,
3113 trailing_row,
3114 "slider.trailing",
3115 "Trailing color",
3116 state.slider_trailing_color,
3117 LayoutStyle::new()
3118 .with_width(142.0)
3119 .with_height(30.0)
3120 .with_flex_shrink(0.0),
3121 );
3122 widgets::color_edit_button_rgb(
3123 ui,
3124 trailing_row,
3125 "slider.trailing_color_button",
3126 state.slider_trailing_picker.value,
3127 color_square_button_options("slider.trailing_color_button")
3128 .accessibility_label("Pick trailing slider color"),
3129 );
3130 if state.slider_trailing_picker_open {
3131 widgets::color_picker(
3132 ui,
3133 body,
3134 "slider.trailing_picker",
3135 &state.slider_trailing_picker,
3136 widgets::ColorPickerOptions::default()
3137 .with_label("Trailing slider color")
3138 .with_action_prefix("slider.trailing_picker"),
3139 );
3140 }
3141 let thumb_row = row(ui, body, "slider.thumb.row", 8.0);
3142 widgets::label(
3143 ui,
3144 thumb_row,
3145 "slider.thumb.label",
3146 "Thumb",
3147 text(12.0, color(166, 176, 190)),
3148 LayoutStyle::new().with_width(64.0),
3149 );
3150 choice_button(
3151 ui,
3152 thumb_row,
3153 "slider.thumb.circle",
3154 "Circle",
3155 state.slider_thumb_shape == SliderThumbChoice::Circle,
3156 );
3157 choice_button(
3158 ui,
3159 thumb_row,
3160 "slider.thumb.square",
3161 "Square",
3162 state.slider_thumb_shape == SliderThumbChoice::Square,
3163 );
3164 choice_button(
3165 ui,
3166 thumb_row,
3167 "slider.thumb.rectangle",
3168 "Rectangle",
3169 state.slider_thumb_shape == SliderThumbChoice::Rectangle,
3170 );
3171 slider_checkbox(
3172 ui,
3173 body,
3174 "slider.steps",
3175 "Use steps",
3176 state.slider_use_steps,
3177 );
3178 let step_row = row(ui, body, "slider.step.row", 10.0);
3179 widgets::label(
3180 ui,
3181 step_row,
3182 "slider.step.label",
3183 "Step value",
3184 text(12.0, color(166, 176, 190)),
3185 LayoutStyle::new().with_width(74.0),
3186 );
3187 slider_number_input(
3188 ui,
3189 step_row,
3190 "slider.step_text",
3191 &state.slider_step_text,
3192 FocusedTextInput::SliderStep,
3193 state,
3194 86.0,
3195 );
3196 slider_checkbox(
3197 ui,
3198 body,
3199 "slider.logarithmic",
3200 "Logarithmic",
3201 state.slider_logarithmic,
3202 );
3203 let clamp_row = row(ui, body, "slider.clamping.row", 8.0);
3204 widgets::label(
3205 ui,
3206 clamp_row,
3207 "slider.clamping.label",
3208 "Clamping",
3209 text(12.0, color(166, 176, 190)),
3210 LayoutStyle::new().with_width(74.0),
3211 );
3212 choice_button(
3213 ui,
3214 clamp_row,
3215 "slider.clamping.never",
3216 "Never",
3217 state.slider_clamping == widgets::SliderClamping::Never,
3218 );
3219 choice_button(
3220 ui,
3221 clamp_row,
3222 "slider.clamping.edits",
3223 "Edits",
3224 state.slider_clamping == widgets::SliderClamping::Edits,
3225 );
3226 choice_button(
3227 ui,
3228 clamp_row,
3229 "slider.clamping.always",
3230 "Always",
3231 state.slider_clamping == widgets::SliderClamping::Always,
3232 );
3233 slider_checkbox(
3234 ui,
3235 body,
3236 "slider.smart_aim",
3237 "Smart aim",
3238 state.slider_smart_aim,
3239 );
3240}
3241
3242fn numeric_inputs(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3243 let body = section(ui, parent, "numeric", "Numeric input");
3244 let row_one = row(ui, body, "numeric.row.values", 10.0);
3245 widgets::drag_value_input(
3246 ui,
3247 row_one,
3248 "numeric.drag_value",
3249 state.numeric_value as f64,
3250 widgets::DragValueOptions::default()
3251 .with_range(widgets::NumericRange::new(0.0, 100.0))
3252 .with_precision(widgets::NumericPrecision::decimals(1))
3253 .with_unit(widgets::NumericUnitFormat::default().suffix(" px"))
3254 .with_action("numeric.drag_value"),
3255 );
3256 widgets::drag_angle(
3257 ui,
3258 row_one,
3259 "numeric.drag_angle",
3260 state.numeric_angle as f64,
3261 widgets::DragValueOptions::default().with_action("numeric.drag_angle"),
3262 );
3263 widgets::drag_angle_tau(
3264 ui,
3265 row_one,
3266 "numeric.drag_angle_tau",
3267 state.numeric_tau as f64,
3268 widgets::DragValueOptions::default().with_action("numeric.drag_angle_tau"),
3269 );
3270 widgets::label(
3271 ui,
3272 body,
3273 "numeric.note",
3274 "Drag values expose spinbutton semantics and unit-aware formatting.",
3275 text(12.0, color(166, 176, 190)),
3276 LayoutStyle::new().with_width_percent(1.0),
3277 );
3278}
3279
3280fn selection_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3281 let body = section(ui, parent, "selection", "Select controls");
3282 let select_width = 180.0;
3283
3284 widgets::label(
3285 ui,
3286 body,
3287 "selection.combo.label",
3288 "Combo box",
3289 text(12.0, color(166, 176, 190)),
3290 LayoutStyle::new().with_width_percent(1.0),
3291 );
3292
3293 let mut options = widgets::ComboBoxOptions::default();
3294 options.accessibility_label = Some("Display density".to_string());
3295 options.text_style = text(13.0, color(230, 236, 246));
3296 options.layout = LayoutStyle::new()
3297 .with_width(select_width)
3298 .with_height(30.0);
3299 let combo = widgets::combo_box(
3300 ui,
3301 body,
3302 "combo.toggle",
3303 state.combo_label.clone(),
3304 state.combo_open,
3305 options,
3306 );
3307 ui.node_mut(combo).action = Some("combo.toggle".into());
3308 let select_options = select_options();
3309 if state.combo_open {
3310 widgets::select_menu_popup(
3311 ui,
3312 body,
3313 "selection.combo_menu",
3314 widgets::AnchoredPopup::new(
3315 UiRect::new(0.0, 27.0, select_width, 30.0),
3316 UiRect::new(0.0, 0.0, 320.0, 308.0),
3317 widgets::PopupPlacement::default(),
3318 ),
3319 &select_options,
3320 &widgets::SelectMenuState {
3321 open: true,
3322 selected: select_options
3323 .iter()
3324 .position(|option| option.label == state.combo_label),
3325 active: select_options
3326 .iter()
3327 .position(|option| option.label == state.combo_label),
3328 },
3329 select_menu_options(select_width).with_action_prefix("selection.combo"),
3330 );
3331 }
3332
3333 widgets::label(
3334 ui,
3335 body,
3336 "selection.menu.label",
3337 "Select menu",
3338 text(12.0, color(166, 176, 190)),
3339 LayoutStyle::new().with_width_percent(1.0),
3340 );
3341 widgets::select_menu(
3342 ui,
3343 body,
3344 "selection.select_menu",
3345 &select_options,
3346 &state.select_menu,
3347 widgets::SelectMenuOptions::default().with_action_prefix("selection.menu"),
3348 );
3349 widgets::label(
3350 ui,
3351 body,
3352 "selection.dropdown.label",
3353 "Dropdown select",
3354 text(12.0, color(166, 176, 190)),
3355 LayoutStyle::new().with_width_percent(1.0),
3356 );
3357 let mut dropdown_options = widgets::DropdownSelectOptions::default();
3358 dropdown_options.menu =
3359 select_menu_options(select_width).with_action_prefix("selection.dropdown");
3360 let dropdown_nodes = widgets::dropdown_select(
3361 ui,
3362 body,
3363 "selection.dropdown",
3364 &select_options,
3365 &state.dropdown,
3366 Some(widgets::AnchoredPopup::new(
3367 UiRect::new(0.0, 0.0, select_width, 30.0),
3368 UiRect::new(0.0, 0.0, 320.0, 308.0),
3369 widgets::PopupPlacement::default(),
3370 )),
3371 dropdown_options,
3372 );
3373 ui.node_mut(dropdown_nodes.trigger).action = Some("selection.dropdown.toggle".into());
3374}
3375
3376fn select_menu_options(width: f32) -> widgets::SelectMenuOptions {
3377 let mut options = widgets::SelectMenuOptions::default();
3378 options.width = width;
3379 options
3380}
3381
3382fn text_input(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3383 let body = section(ui, parent, "text_input", "Text input");
3384 let mut options = TextInputOptions::default();
3385 options.placeholder = "Type here".to_string();
3386 options.layout = LayoutStyle::new().with_width(300.0).with_height(36.0);
3387 options.text_style = text(13.0, color(230, 236, 246));
3388 options.placeholder_style = text(13.0, color(144, 156, 174));
3389 options.edit_action = Some("text.input.edit".into());
3390 options.focused = state.focused_text == Some(FocusedTextInput::Editable);
3391 options.caret_visible = caret_visible(state.caret_phase);
3392 widgets::text_input(ui, body, "text.input", &state.text, options);
3393
3394 let mut selectable_options = TextInputOptions::default();
3395 selectable_options.layout = LayoutStyle::new().with_width(360.0).with_height(36.0);
3396 selectable_options.text_style = text(13.0, color(196, 210, 230));
3397 selectable_options.read_only = true;
3398 selectable_options.selectable = true;
3399 selectable_options.focused = state.focused_text == Some(FocusedTextInput::Selectable);
3400 selectable_options.edit_action = Some("text.selectable.edit".into());
3401 selectable_options.caret_visible = caret_visible(state.caret_phase);
3402 widgets::text_input(
3403 ui,
3404 body,
3405 "text.selectable",
3406 &state.selectable_text,
3407 selectable_options,
3408 );
3409
3410 let mut singleline = TextInputOptions::default();
3411 singleline.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
3412 singleline.text_style = text(13.0, color(230, 236, 246));
3413 singleline.placeholder = "Single line".to_string();
3414 singleline.edit_action = Some("text.singleline.edit".into());
3415 singleline.focused = state.focused_text == Some(FocusedTextInput::Singleline);
3416 singleline.caret_visible = caret_visible(state.caret_phase);
3417 widgets::singleline_text_input(
3418 ui,
3419 body,
3420 "text.singleline",
3421 &state.singleline_text,
3422 singleline,
3423 );
3424
3425 let mut multiline = TextInputOptions::default();
3426 multiline.layout = LayoutStyle::new().with_width(360.0).with_height(72.0);
3427 multiline.text_style = text(13.0, color(230, 236, 246));
3428 multiline.edit_action = Some("text.multiline.edit".into());
3429 multiline.focused = state.focused_text == Some(FocusedTextInput::Multiline);
3430 multiline.caret_visible = caret_visible(state.caret_phase);
3431 widgets::multiline_text_input(ui, body, "text.multiline", &state.multiline_text, multiline);
3432
3433 let mut area = TextInputOptions::default();
3434 area.layout = LayoutStyle::new().with_width(360.0).with_height(66.0);
3435 area.text_style = text(13.0, color(230, 236, 246));
3436 area.edit_action = Some("text.area.edit".into());
3437 area.focused = state.focused_text == Some(FocusedTextInput::TextArea);
3438 area.caret_visible = caret_visible(state.caret_phase);
3439 widgets::text_area(ui, body, "text.area", &state.text_area_text, area);
3440
3441 let mut code = TextInputOptions::default();
3442 code.layout = LayoutStyle::new().with_width(360.0).with_height(88.0);
3443 code.edit_action = Some("text.code_editor.edit".into());
3444 code.focused = state.focused_text == Some(FocusedTextInput::CodeEditor);
3445 code.caret_visible = caret_visible(state.caret_phase);
3446 widgets::code_editor(ui, body, "text.code_editor", &state.code_editor_text, code);
3447
3448 let mut search = TextInputOptions::default();
3449 search.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
3450 search.text_style = text(13.0, color(230, 236, 246));
3451 search.edit_action = Some("text.search.edit".into());
3452 search.focused = state.focused_text == Some(FocusedTextInput::Search);
3453 search.caret_visible = caret_visible(state.caret_phase);
3454 widgets::search_input(ui, body, "text.search", &state.search_text, search);
3455
3456 let mut password = TextInputOptions::default();
3457 password.layout = LayoutStyle::new().with_width(300.0).with_height(34.0);
3458 password.text_style = text(13.0, color(230, 236, 246));
3459 password.edit_action = Some("text.password.edit".into());
3460 password.focused = state.focused_text == Some(FocusedTextInput::Password);
3461 password.caret_visible = caret_visible(state.caret_phase);
3462 widgets::password_input(ui, body, "text.password", &state.password_text, password);
3463
3464 let mut helper = TextInputOptions::default();
3465 helper.read_only = true;
3466 helper.selectable = true;
3467 helper.layout = LayoutStyle::new().with_width(360.0).with_height(36.0);
3468 helper.text_style = text(13.0, color(196, 210, 230));
3469 helper.edit_action = Some("text.selectable_helper.edit".into());
3470 helper.focused = state.focused_text == Some(FocusedTextInput::SelectableHelper);
3471 helper.caret_visible = caret_visible(state.caret_phase);
3472 widgets::selectable_text(
3473 ui,
3474 body,
3475 "text.selectable_helper",
3476 &state.selectable_text,
3477 helper,
3478 );
3479}
3480
3481fn date_picker(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3482 let body = section(ui, parent, "date", "Date picker");
3483 let controls = row(ui, body, "date.options", 8.0);
3484 choice_button(
3485 ui,
3486 controls,
3487 "date.week.sunday",
3488 "Sun first",
3489 state.date.first_weekday == widgets::Weekday::Sunday,
3490 );
3491 choice_button(
3492 ui,
3493 controls,
3494 "date.week.monday",
3495 "Mon first",
3496 state.date.first_weekday == widgets::Weekday::Monday,
3497 );
3498 let mut range_button =
3499 widgets::ButtonOptions::new(LayoutStyle::new().with_width(92.0).with_height(28.0))
3500 .with_action("date.range.toggle");
3501 range_button.visual = if state.date.min.is_some() || state.date.max.is_some() {
3502 button_visual(48, 112, 184)
3503 } else {
3504 button_visual(38, 46, 58)
3505 };
3506 range_button.hovered_visual = Some(button_visual(65, 86, 106));
3507 range_button.text_style = text(12.0, color(238, 244, 252));
3508 widgets::button(
3509 ui,
3510 controls,
3511 "date.range.toggle",
3512 "Limit range",
3513 range_button,
3514 );
3515 widgets::date_picker(
3516 ui,
3517 body,
3518 "date.picker",
3519 &state.date,
3520 widgets::DatePickerOptions::default().with_action_prefix("date"),
3521 );
3522 widgets::label(
3523 ui,
3524 body,
3525 "date.selected",
3526 format!(
3527 "Selected: {}",
3528 state
3529 .date
3530 .selected
3531 .map_or_else(|| "None".to_string(), CalendarDate::iso_string)
3532 ),
3533 text(11.0, color(154, 166, 184)),
3534 LayoutStyle::new().with_width_percent(1.0),
3535 );
3536}
3537
3538fn color_picker(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3539 let body = section(ui, parent, "color", "Color picker");
3540 widgets::color_picker(
3541 ui,
3542 body,
3543 "color.picker",
3544 &state.color,
3545 widgets::ColorPickerOptions::default()
3546 .with_action_prefix("color")
3547 .with_copy_hex_action("color.copy_hex")
3548 .with_copy_hex_label("Copy"),
3549 );
3550 if let Some(hex) = &state.color_copied_hex {
3551 widgets::label(
3552 ui,
3553 body,
3554 "color.copied",
3555 format!("Copied {hex}"),
3556 text(11.0, color(154, 166, 184)),
3557 LayoutStyle::new().with_width_percent(1.0),
3558 );
3559 }
3560}
3561
3562fn color_buttons(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3563 let body = section(ui, parent, "color_buttons", "Color buttons");
3564 let current_color = state.color.value;
3565
3566 widgets::label(
3567 ui,
3568 body,
3569 "color_buttons.edit_label",
3570 "Color edit button",
3571 text(12.0, color(166, 176, 190)),
3572 LayoutStyle::new().with_width_percent(1.0),
3573 );
3574 let edit_row = row(ui, body, "color_buttons.edit_row", 8.0);
3575 widgets::color_edit_button_rgb(
3576 ui,
3577 edit_row,
3578 "color_buttons.compact",
3579 current_color,
3580 color_square_button_options("color_buttons.compact").accessibility_label("Edit RGB color"),
3581 );
3582 widgets::label(
3583 ui,
3584 edit_row,
3585 "color_buttons.hex_value",
3586 widgets::format_hex_color(current_color, false),
3587 text(12.0, color(220, 228, 238)),
3588 LayoutStyle::new().with_width(92.0),
3589 );
3590
3591 widgets::label(
3592 ui,
3593 body,
3594 "color_buttons.format_label",
3595 "Value formats",
3596 text(12.0, color(166, 176, 190)),
3597 LayoutStyle::new().with_width_percent(1.0),
3598 );
3599 let rgb_row = row(ui, body, "color_buttons.rgb_row", 8.0);
3600 widgets::label(
3601 ui,
3602 rgb_row,
3603 "color_buttons.rgb_label",
3604 "RGB",
3605 text(12.0, color(186, 198, 216)),
3606 LayoutStyle::new().with_width(48.0),
3607 );
3608 widgets::color_edit_button_rgb(
3609 ui,
3610 rgb_row,
3611 "color_buttons.rgb",
3612 current_color,
3613 color_value_button_options("color_buttons.rgb", 180.0),
3614 );
3615 let rgba_row = row(ui, body, "color_buttons.rgba_row", 8.0);
3616 widgets::label(
3617 ui,
3618 rgba_row,
3619 "color_buttons.rgba_label",
3620 "RGBA",
3621 text(12.0, color(186, 198, 216)),
3622 LayoutStyle::new().with_width(48.0),
3623 );
3624 widgets::color_edit_button_rgba(
3625 ui,
3626 rgba_row,
3627 "color_buttons.rgba",
3628 current_color,
3629 color_value_button_options("color_buttons.rgba", 230.0),
3630 );
3631 let hsva_row = row(ui, body, "color_buttons.hsva_row", 8.0);
3632 widgets::label(
3633 ui,
3634 hsva_row,
3635 "color_buttons.hsva_label",
3636 "HSVA",
3637 text(12.0, color(186, 198, 216)),
3638 LayoutStyle::new().with_width(48.0),
3639 );
3640 widgets::color_edit_button_hsva(
3641 ui,
3642 hsva_row,
3643 "color_buttons.hsva",
3644 current_color,
3645 color_value_button_options("color_buttons.hsva", 260.0),
3646 );
3647
3648 widgets::label(
3649 ui,
3650 body,
3651 "color_buttons.field_label",
3652 "2D color field",
3653 text(12.0, color(166, 176, 190)),
3654 LayoutStyle::new().with_width_percent(1.0),
3655 );
3656 widgets::color_picker_hsva_2d(
3657 ui,
3658 body,
3659 "color_buttons.hsva_2d",
3660 state.color.hsv(),
3661 widgets::ColorHsva2dOptions::default()
3662 .with_layout(LayoutStyle::new().with_width(204.0).with_height(112.0))
3663 .with_action_prefix("color_buttons.hsva_2d"),
3664 );
3665 widgets::label(
3666 ui,
3667 body,
3668 "color_buttons.status",
3669 format!("Last activated: {}", state.color_button_status),
3670 text(11.0, color(154, 166, 184)),
3671 LayoutStyle::new().with_width_percent(1.0),
3672 );
3673}
3674
3675fn menu_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3676 let body = section(ui, parent, "menus", "Menus");
3677 let menus = menu_bar_menus(state.menu_autosave, state.menu_grid);
3678 let active_items = state
3679 .menu_bar
3680 .open_menu
3681 .and_then(|index| menus.get(index))
3682 .map(|menu| menu.items.clone())
3683 .unwrap_or_default();
3684 widgets::menu_bar(
3685 ui,
3686 body,
3687 "menus.menu_bar",
3688 &menus,
3689 &state.menu_bar,
3690 None,
3691 widgets::MenuBarOptions::default().with_action_prefix("menus.bar"),
3692 );
3693
3694 if !active_items.is_empty() {
3695 widgets::menu_list(
3696 ui,
3697 body,
3698 "menus.menu_list",
3699 &active_items,
3700 state.menu_bar.active_item,
3701 widgets::MenuListOptions::default().with_action_prefix("menus.item"),
3702 );
3703 if let Some(active_item) = state.menu_bar.active_item {
3704 if let Some(children) = active_items
3705 .get(active_item)
3706 .and_then(|item| item.children())
3707 {
3708 widgets::menu_list_popup(
3709 ui,
3710 body,
3711 "menus.submenu",
3712 widgets::AnchoredPopup::new(
3713 UiRect::new(
3714 0.0,
3715 40.0 + menu_item_top_offset(&active_items, active_item),
3716 240.0,
3717 menu_item_height(active_items.get(active_item)),
3718 ),
3719 UiRect::new(0.0, 0.0, 680.0, 468.0),
3720 widgets::PopupPlacement::new(
3721 widgets::PopupSide::Right,
3722 widgets::PopupAlign::Start,
3723 )
3724 .with_offset(4.0),
3725 ),
3726 children,
3727 Some(0),
3728 widgets::MenuListOptions::default().with_action_prefix("menus.item"),
3729 );
3730 }
3731 }
3732 }
3733 divider(ui, body, "menus.divider.buttons");
3734 let button_row = row(ui, body, "menus.buttons", 8.0);
3735 let button_items = menu_items(state.menu_autosave);
3736 widgets::menu_button(
3737 ui,
3738 button_row,
3739 "menus.menu_button",
3740 "Menu button",
3741 &button_items,
3742 &state.menu_button,
3743 None,
3744 widgets::MenuButtonOptions::default().with_action("menus.menu_button"),
3745 );
3746 widgets::image_text_menu_button(
3747 ui,
3748 button_row,
3749 "menus.image_text_menu_button",
3750 "Image text",
3751 icon_image(BuiltInIcon::Folder),
3752 &button_items,
3753 &state.image_text_menu_button,
3754 None,
3755 widgets::MenuButtonOptions::default().with_action("menus.image_text_menu_button"),
3756 );
3757 widgets::image_menu_button(
3758 ui,
3759 button_row,
3760 "menus.image_menu_button",
3761 icon_image(BuiltInIcon::Settings),
3762 &button_items,
3763 &state.image_menu_button,
3764 None,
3765 widgets::MenuButtonOptions::default().with_action("menus.image_menu_button"),
3766 );
3767 if state.menu_button.open || state.image_text_menu_button.open || state.image_menu_button.open {
3768 let active = state
3769 .menu_button
3770 .navigation
3771 .active_path
3772 .first()
3773 .copied()
3774 .or_else(|| {
3775 state
3776 .image_text_menu_button
3777 .navigation
3778 .active_path
3779 .first()
3780 .copied()
3781 })
3782 .or_else(|| {
3783 state
3784 .image_menu_button
3785 .navigation
3786 .active_path
3787 .first()
3788 .copied()
3789 });
3790 widgets::menu_list(
3791 ui,
3792 body,
3793 "menus.button_menu",
3794 &button_items,
3795 active,
3796 widgets::MenuListOptions::default().with_action_prefix("menus.item"),
3797 );
3798 }
3799
3800 let context_row = row(ui, body, "menus.context.controls", 8.0);
3801 button(
3802 ui,
3803 context_row,
3804 "menus.context.open",
3805 "Open context",
3806 "menus.context.open",
3807 button_visual(48, 112, 184),
3808 );
3809 button(
3810 ui,
3811 context_row,
3812 "menus.context.close",
3813 "Close",
3814 "menus.context.close",
3815 button_visual(58, 78, 96),
3816 );
3817 let mut context_options =
3818 widgets::MenuListOptions::default().with_action_prefix("menus.context");
3819 context_options.width = 180.0;
3820 context_options.max_visible_rows = 4;
3821 let _ = widgets::context_menu(
3822 ui,
3823 parent,
3824 "menus.context_menu",
3825 &button_items,
3826 &state.context_menu,
3827 UiRect::new(0.0, 0.0, 180.0, 120.0),
3828 widgets::PopupPlacement::default(),
3829 context_options,
3830 );
3831}
3832
3833fn command_palette(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3834 let body = section(ui, parent, "command_palette", "Command palette");
3835 let items = command_palette_items();
3836 let mut options =
3837 widgets::CommandPaletteOptions::default().with_action_prefix("command_palette");
3838 options.width = 480.0;
3839 options.row_height = 44.0;
3840 options.max_visible_rows = 5;
3841 options.text_style = text(13.0, color(238, 244, 252));
3842 options.muted_text_style = text(11.0, color(166, 178, 196));
3843 widgets::command_palette(
3844 ui,
3845 body,
3846 "command_palette.panel",
3847 &items,
3848 &state.command_palette,
3849 None,
3850 options,
3851 );
3852 widgets::label(
3853 ui,
3854 body,
3855 "command_palette.last",
3856 format!("Last command: {}", state.last_command),
3857 text(12.0, color(154, 166, 184)),
3858 LayoutStyle::new().with_width_percent(1.0),
3859 );
3860}
3861
3862fn progress_indicator(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3863 let body = section(ui, parent, "progress", "Progress indicator");
3864 let animated = smooth_loop(state.progress_phase * 0.85, 0.0) * 100.0;
3865 let mut progress = widgets::ProgressIndicatorOptions::default();
3866 progress.layout = LayoutStyle::new().with_width_percent(1.0).with_height(10.0);
3867 progress.accessibility_label = Some("Progress".to_string());
3868 widgets::progress_indicator(
3869 ui,
3870 body,
3871 "progress.primary",
3872 widgets::ProgressIndicatorValue::percent(animated),
3873 progress,
3874 );
3875 let compact_value = smooth_loop(state.progress_phase * 1.15, 0.7) * 100.0;
3876 let mut compact = widgets::ProgressIndicatorOptions::default();
3877 compact.layout = LayoutStyle::new().with_width_percent(1.0).with_height(6.0);
3878 compact.fill_visual = UiVisual::panel(color(111, 203, 159), None, 3.0);
3879 widgets::progress_indicator(
3880 ui,
3881 body,
3882 "progress.compact",
3883 widgets::ProgressIndicatorValue::percent(compact_value),
3884 compact,
3885 );
3886 let warning_value = smooth_loop(state.progress_phase * 0.65, 1.4) * 100.0;
3887 let mut warning = widgets::ProgressIndicatorOptions::default();
3888 warning.layout = LayoutStyle::new().with_width_percent(1.0).with_height(14.0);
3889 warning.fill_visual = UiVisual::panel(color(232, 186, 88), None, 4.0);
3890 widgets::progress_indicator(
3891 ui,
3892 body,
3893 "progress.warning",
3894 widgets::ProgressIndicatorValue::percent(warning_value),
3895 warning,
3896 );
3897 let spinner_row = row(ui, body, "progress.spinner.row", 8.0);
3898 widgets::spinner(
3899 ui,
3900 spinner_row,
3901 "progress.spinner",
3902 widgets::SpinnerOptions::default()
3903 .with_phase(state.progress_phase)
3904 .with_accessibility_label("Loading spinner"),
3905 );
3906 widgets::label(
3907 ui,
3908 spinner_row,
3909 "progress.spinner.label",
3910 "Spinner",
3911 text(12.0, color(196, 210, 230)),
3912 LayoutStyle::new().with_width_percent(1.0),
3913 );
3914}
3915
3916fn list_and_table_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
3917 let body = section(ui, parent, "lists_tables", "Lists and tables");
3918
3919 let scroll_shell = row(ui, body, "lists_tables.scroll_area.shell", 8.0);
3920 let nested_scroll = widgets::scroll_area(
3921 ui,
3922 scroll_shell,
3923 "lists_tables.scroll_area",
3924 ScrollAxes::VERTICAL,
3925 LayoutStyle::column()
3926 .with_width(0.0)
3927 .with_flex_grow(1.0)
3928 .with_height(92.0),
3929 );
3930 ui.node_mut(nested_scroll).action = Some("lists_tables.scroll_area.scroll".into());
3931 if let Some(scroll) = ui.node_mut(nested_scroll).scroll.as_mut() {
3932 scroll.offset.y = state.list_scroll;
3933 }
3934 for index in 0..6 {
3935 widgets::label(
3936 ui,
3937 nested_scroll,
3938 format!("lists_tables.scroll_area.row.{index}"),
3939 format!("Scroll row {}", index + 1),
3940 text(12.0, color(200, 212, 228)),
3941 LayoutStyle::new()
3942 .with_width_percent(1.0)
3943 .with_height(26.0)
3944 .with_flex_shrink(0.0),
3945 );
3946 }
3947 widgets::scrollbar(
3948 ui,
3949 scroll_shell,
3950 "lists_tables.scroll_area.scrollbar",
3951 scroll_state(state.list_scroll, 92.0, 6.0 * 26.0),
3952 widgets::ScrollAxis::Vertical,
3953 widgets::ScrollbarOptions::default()
3954 .with_layout(LayoutStyle::size(8.0, 92.0))
3955 .with_track_size(UiSize::new(8.0, 92.0))
3956 .with_action("lists_tables.scroll_area.scrollbar"),
3957 );
3958
3959 widgets::table_header(ui, body, "lists_tables.table_header", &table_columns());
3960
3961 let virtual_shell = row(ui, body, "lists_tables.virtual_list.shell", 8.0);
3962 let virtual_list = widgets::virtual_list(
3963 ui,
3964 virtual_shell,
3965 "lists_tables.virtual_list",
3966 widgets::VirtualListSpec {
3967 row_count: 24,
3968 row_height: 28.0,
3969 viewport_height: 112.0,
3970 scroll_offset: state.virtual_scroll,
3971 overscan: 1,
3972 },
3973 |ui, row_parent, row| {
3974 widgets::label(
3975 ui,
3976 row_parent,
3977 format!("lists_tables.virtual_list.row.{row}"),
3978 format!("Virtual row {}", row + 1),
3979 text(12.0, color(214, 224, 238)),
3980 LayoutStyle::new()
3981 .with_width_percent(1.0)
3982 .with_height(28.0)
3983 .with_flex_shrink(0.0),
3984 );
3985 },
3986 );
3987 ui.node_mut(virtual_list).action = Some("lists_tables.virtual_list.scroll".into());
3988 widgets::scrollbar(
3989 ui,
3990 virtual_shell,
3991 "lists_tables.virtual_list.scrollbar",
3992 scroll_state(state.virtual_scroll, 112.0, 24.0 * 28.0),
3993 widgets::ScrollAxis::Vertical,
3994 widgets::ScrollbarOptions::default()
3995 .with_layout(LayoutStyle::size(8.0, 112.0))
3996 .with_track_size(UiSize::new(8.0, 112.0))
3997 .with_action("lists_tables.virtual_list.scrollbar"),
3998 );
3999
4000 let table_shell = row(ui, body, "lists_tables.data_table.shell", 8.0);
4001 let table_scroll = widgets::scroll_area(
4002 ui,
4003 table_shell,
4004 "lists_tables.data_table",
4005 ScrollAxes::VERTICAL,
4006 LayoutStyle::column()
4007 .with_width(0.0)
4008 .with_flex_grow(1.0)
4009 .with_height(128.0),
4010 );
4011 ui.node_mut(table_scroll).action = Some("lists_tables.data_table.scroll".into());
4012 if let Some(scroll) = ui.node_mut(table_scroll).scroll.as_mut() {
4013 scroll.offset.y = state.table_scroll;
4014 }
4015 for row_index in 0..16 {
4016 data_table_row(ui, table_scroll, row_index, state);
4017 }
4018 widgets::scrollbar(
4019 ui,
4020 table_shell,
4021 "lists_tables.data_table.scrollbar",
4022 scroll_state(state.table_scroll, 128.0, 16.0 * 28.0),
4023 widgets::ScrollAxis::Vertical,
4024 widgets::ScrollbarOptions::default()
4025 .with_layout(LayoutStyle::size(8.0, 128.0))
4026 .with_track_size(UiSize::new(8.0, 128.0))
4027 .with_action("lists_tables.data_table.scrollbar"),
4028 );
4029
4030 let columns = vec![
4031 widgets::DataTableColumn::new("name", "Virtualized", 160.0),
4032 widgets::DataTableColumn::new("status", "Status", 110.0),
4033 widgets::DataTableColumn::new("value", "Value", 70.0)
4034 .with_alignment(widgets::DataCellAlignment::End),
4035 ];
4036 let mut table_options = widgets::DataTableOptions::default()
4037 .with_row_action_prefix("lists_tables.virtualized_table.row")
4038 .with_cell_action_prefix("lists_tables.virtualized_table.cell");
4039 table_options.selection = state.table_selection.clone();
4040 widgets::virtualized_data_table(
4041 ui,
4042 body,
4043 "lists_tables.virtualized_table",
4044 &columns,
4045 widgets::VirtualDataTableSpec {
4046 row_count: 32,
4047 row_height: 28.0,
4048 viewport_width: 420.0,
4049 viewport_height: 128.0,
4050 scroll_offset: UiPoint::new(0.0, state.table_scroll),
4051 overscan_rows: 1,
4052 },
4053 table_options,
4054 |ui, cell_parent, cell| {
4055 let value = match cell.column {
4056 0 => format!("Virtual row {}", cell.row + 1),
4057 1 if cell.row % 2 == 0 => "Ready".to_string(),
4058 1 => "Pending".to_string(),
4059 _ => format!("{}%", 30 + cell.row * 2),
4060 };
4061 widgets::label(
4062 ui,
4063 cell_parent,
4064 format!(
4065 "lists_tables.virtualized_table.cell.{}.{}.label",
4066 cell.row, cell.column
4067 ),
4068 value,
4069 text(12.0, color(220, 228, 238)),
4070 LayoutStyle::new().with_width_percent(1.0),
4071 );
4072 },
4073 );
4074}
4075
4076fn data_table_row(ui: &mut UiDocument, parent: UiNodeId, row_index: usize, state: &ShowcaseState) {
4077 let selected = state.table_selection.contains_row(row_index);
4078 let row = ui.add_child(
4079 parent,
4080 UiNode::container(
4081 format!("lists_tables.data_table.row.{row_index}"),
4082 LayoutStyle::row()
4083 .with_width_percent(1.0)
4084 .with_height(28.0)
4085 .with_flex_shrink(0.0),
4086 )
4087 .with_input(operad::InputBehavior::BUTTON)
4088 .with_action(format!("lists_tables.data_table.row.{row_index}"))
4089 .with_visual(if selected {
4090 UiVisual::panel(color(45, 73, 109), None, 0.0)
4091 } else {
4092 UiVisual::TRANSPARENT
4093 }),
4094 );
4095 let values = [
4096 format!("Item {}", row_index + 1),
4097 if row_index % 2 == 0 {
4098 "Ready".to_string()
4099 } else {
4100 "Pending".to_string()
4101 },
4102 format!("{}%", 40 + row_index * 3),
4103 ];
4104 let widths = [0.42, 0.33, 0.25];
4105 for (column, value) in values.into_iter().enumerate() {
4106 let cell = ui.add_child(
4107 row,
4108 UiNode::container(
4109 format!("lists_tables.data_table.cell.{row_index}.{column}"),
4110 LayoutStyle::new()
4111 .with_width_percent(widths[column])
4112 .with_height_percent(1.0)
4113 .padding(6.0),
4114 )
4115 .with_input(operad::InputBehavior::BUTTON)
4116 .with_action(format!("lists_tables.data_table.cell.{row_index}.{column}")),
4117 );
4118 widgets::label(
4119 ui,
4120 cell,
4121 format!("lists_tables.data_table.cell.{row_index}.{column}.label"),
4122 value,
4123 text(12.0, color(222, 230, 240)),
4124 LayoutStyle::new().with_width_percent(1.0),
4125 );
4126 }
4127}
4128
4129fn property_inspector(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4130 let body = section(ui, parent, "property_inspector", "Property inspector");
4131 widgets::label(
4132 ui,
4133 body,
4134 "property_inspector.target",
4135 "Inspecting: Styling preview",
4136 text(12.0, color(196, 210, 230)),
4137 LayoutStyle::new().with_width_percent(1.0),
4138 );
4139 let mut options = widgets::PropertyInspectorOptions::default();
4140 options.selected_index = Some(0);
4141 options.label_width = 120.0;
4142 options.row_height = 30.0;
4143 widgets::property_inspector_grid(
4144 ui,
4145 body,
4146 "property_inspector.grid",
4147 &[
4148 widgets::PropertyGridRow::new("target", "Widget", "Button preview").read_only(),
4149 widgets::PropertyGridRow::new(
4150 "inner",
4151 "Inner margin",
4152 format!("{:.0}px", state.styling.inner_margin),
4153 )
4154 .with_kind(widgets::PropertyValueKind::Number),
4155 widgets::PropertyGridRow::new(
4156 "outer",
4157 "Outer margin",
4158 format!("{:.0}px", state.styling.outer_margin),
4159 )
4160 .with_kind(widgets::PropertyValueKind::Number),
4161 widgets::PropertyGridRow::new(
4162 "radius",
4163 "Corner radius",
4164 format!("{:.0}px", state.styling.corner_radius),
4165 )
4166 .with_kind(widgets::PropertyValueKind::Number),
4167 widgets::PropertyGridRow::new(
4168 "stroke",
4169 "Stroke",
4170 format!("{:.1}px", state.styling.stroke_width),
4171 )
4172 .with_kind(widgets::PropertyValueKind::Number)
4173 .changed(),
4174 widgets::PropertyGridRow::new("state", "Source", "Styling widget").read_only(),
4175 ],
4176 options,
4177 );
4178}
4179
4180fn tree_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4181 let body = section(ui, parent, "trees", "Tree view");
4182 widgets::tree_view(
4183 ui,
4184 body,
4185 "trees.tree_view",
4186 &tree_items(),
4187 &state.tree,
4188 widgets::TreeViewOptions::default().with_row_action_prefix("trees.tree"),
4189 );
4190 widgets::outliner(
4191 ui,
4192 body,
4193 "trees.outliner",
4194 &tree_items(),
4195 &state.outliner,
4196 widgets::TreeViewOptions::default().with_row_action_prefix("trees.outliner"),
4197 );
4198}
4199
4200fn tab_split_dock_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4201 let body = section(ui, parent, "layout_widgets", "Layout panels");
4202 let shell = ui.add_child(
4203 body,
4204 UiNode::container(
4205 "layout_widgets.egui_shell",
4206 LayoutStyle::column()
4207 .with_width_percent(1.0)
4208 .with_height(340.0)
4209 .with_flex_shrink(0.0),
4210 )
4211 .with_visual(UiVisual::panel(
4212 color(13, 17, 23),
4213 Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
4214 0.0,
4215 )),
4216 );
4217 let panels = row(ui, shell, "layout_widgets.egui_panels", 0.0);
4218 ui.node_mut(panels).style.layout.size.height = operad::length(340.0);
4219 let inspector = ui.add_child(
4220 panels,
4221 UiNode::container(
4222 "layout_widgets.inspector_panel",
4223 LayoutStyle::column()
4224 .with_width(230.0)
4225 .with_height_percent(1.0)
4226 .with_flex_shrink(0.0),
4227 )
4228 .with_visual(UiVisual::panel(
4229 color(18, 22, 29),
4230 Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
4231 0.0,
4232 )),
4233 );
4234 egui_panel_contents(
4235 ui,
4236 inspector,
4237 "layout.inspector",
4238 "Inspector",
4239 state.layout_inspector_scroll,
4240 );
4241
4242 let assets = ui.add_child(
4243 panels,
4244 UiNode::container(
4245 "layout_widgets.assets_panel",
4246 LayoutStyle::column()
4247 .with_width(0.0)
4248 .with_height_percent(1.0)
4249 .with_flex_grow(1.0),
4250 )
4251 .with_visual(UiVisual::panel(
4252 color(15, 19, 25),
4253 Some(StrokeStyle::new(color(54, 65, 80), 1.0)),
4254 0.0,
4255 )),
4256 );
4257 egui_panel_contents(
4258 ui,
4259 assets,
4260 "layout.assets",
4261 "Assets",
4262 state.layout_assets_scroll,
4263 );
4264}
4265
4266fn container_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4267 let body = section(ui, parent, "containers", "Containers");
4268
4269 let frame = widgets::frame(
4270 ui,
4271 body,
4272 "containers.frame",
4273 widgets::FrameOptions::default().with_layout(
4274 LayoutStyle::column()
4275 .with_width_percent(1.0)
4276 .with_height(64.0)
4277 .with_padding(8.0)
4278 .with_gap(6.0),
4279 ),
4280 );
4281 widgets::strong_label(
4282 ui,
4283 frame,
4284 "containers.frame.title",
4285 "Frame",
4286 LayoutStyle::new().with_width_percent(1.0),
4287 );
4288 widgets::weak_label(
4289 ui,
4290 frame,
4291 "containers.frame.body",
4292 "Default framed surface with padding, stroke, and clipping.",
4293 LayoutStyle::new().with_width_percent(1.0),
4294 );
4295
4296 let group = widgets::group(ui, body, "containers.group");
4297 widgets::label(
4298 ui,
4299 group,
4300 "containers.group.label",
4301 "Group helper",
4302 text(12.0, color(220, 228, 238)),
4303 LayoutStyle::new().with_width_percent(1.0),
4304 );
4305 let generic_panel = widgets::panel(
4306 ui,
4307 body,
4308 "containers.panel",
4309 widgets::PanelOptions::group().with_layout(
4310 LayoutStyle::column()
4311 .with_width_percent(1.0)
4312 .with_height(44.0)
4313 .with_padding(8.0),
4314 ),
4315 );
4316 widgets::label(
4317 ui,
4318 generic_panel,
4319 "containers.panel.label",
4320 "Generic panel",
4321 text(12.0, color(220, 228, 238)),
4322 LayoutStyle::new().with_width_percent(1.0),
4323 );
4324 let group_panel = widgets::group_panel(ui, body, "containers.group_panel");
4325 widgets::label(
4326 ui,
4327 group_panel,
4328 "containers.group_panel.label",
4329 "Group panel",
4330 text(12.0, color(220, 228, 238)),
4331 LayoutStyle::new().with_width_percent(1.0),
4332 );
4333
4334 widgets::separator(
4335 ui,
4336 body,
4337 "containers.separator",
4338 widgets::SeparatorOptions::default(),
4339 );
4340 widgets::spacer(
4341 ui,
4342 body,
4343 "containers.spacer",
4344 LayoutStyle::new()
4345 .with_width_percent(1.0)
4346 .with_height(8.0)
4347 .with_flex_shrink(0.0),
4348 );
4349
4350 let grid = widgets::grid(
4351 ui,
4352 body,
4353 "containers.grid",
4354 widgets::GridOptions::default().with_layout(
4355 LayoutStyle::column()
4356 .with_width_percent(1.0)
4357 .with_height(78.0)
4358 .with_gap(4.0),
4359 ),
4360 );
4361 for row_index in 0..2 {
4362 let row = widgets::grid_row(
4363 ui,
4364 grid,
4365 format!("containers.grid.row.{row_index}"),
4366 widgets::GridRowOptions::default(),
4367 );
4368 for column_index in 0..3 {
4369 widgets::grid_text_cell(
4370 ui,
4371 row,
4372 format!("containers.grid.row.{row_index}.cell.{column_index}"),
4373 format!("R{} C{}", row_index + 1, column_index + 1),
4374 widgets::GridCellOptions {
4375 text_style: text(12.0, color(214, 224, 238)),
4376 ..Default::default()
4377 },
4378 );
4379 }
4380 }
4381
4382 widgets::sides(
4383 ui,
4384 body,
4385 "containers.sides",
4386 widgets::SidesOptions::default()
4387 .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
4388 .with_gap(8.0)
4389 .with_visual(UiVisual::panel(
4390 color(20, 25, 32),
4391 Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
4392 4.0,
4393 )),
4394 |ui, left| {
4395 widgets::label(
4396 ui,
4397 left,
4398 "containers.sides.left.label",
4399 "Left side",
4400 text(12.0, color(220, 228, 238)),
4401 LayoutStyle::new().with_width_percent(1.0),
4402 );
4403 },
4404 |ui, right| {
4405 widgets::label(
4406 ui,
4407 right,
4408 "containers.sides.right.label",
4409 "Right side",
4410 text(12.0, color(220, 228, 238)),
4411 LayoutStyle::new().with_width_percent(1.0),
4412 );
4413 },
4414 );
4415
4416 widgets::columns(
4417 ui,
4418 body,
4419 "containers.columns",
4420 3,
4421 widgets::ColumnsOptions::default()
4422 .with_layout(LayoutStyle::row().with_width_percent(1.0).with_height(48.0))
4423 .with_gap(8.0),
4424 |ui, column, index| {
4425 widgets::label(
4426 ui,
4427 column,
4428 format!("containers.columns.{index}.label"),
4429 format!("Column {}", index + 1),
4430 text(12.0, color(220, 228, 238)),
4431 LayoutStyle::new().with_width_percent(1.0),
4432 );
4433 },
4434 );
4435
4436 let indented = widgets::indented_section(
4437 ui,
4438 body,
4439 "containers.indented",
4440 widgets::IndentOptions::default().with_amount(24.0),
4441 );
4442 widgets::label(
4443 ui,
4444 indented,
4445 "containers.indented.label",
4446 "Indented section",
4447 text(12.0, color(196, 210, 230)),
4448 LayoutStyle::new().with_width_percent(1.0),
4449 );
4450
4451 widgets::resize_container(
4452 ui,
4453 body,
4454 "containers.resize_container",
4455 widgets::ResizeContainerOptions::default().with_layout(
4456 LayoutStyle::column()
4457 .with_width_percent(1.0)
4458 .with_height(92.0)
4459 .with_flex_shrink(0.0),
4460 ),
4461 |ui, content| {
4462 widgets::label(
4463 ui,
4464 content,
4465 "containers.resize_container.label",
4466 "Resize container",
4467 text(12.0, color(220, 228, 238)),
4468 LayoutStyle::new().with_width_percent(1.0),
4469 );
4470 },
4471 );
4472 widgets::resize_handle(
4473 ui,
4474 body,
4475 "containers.resize_handle",
4476 widgets::ResizeHandleOptions::default()
4477 .with_layout(LayoutStyle::size(20.0, 20.0))
4478 .accessibility_label("Inline resize handle"),
4479 );
4480
4481 widgets::scene(
4482 ui,
4483 body,
4484 "containers.scene",
4485 vec![
4486 ScenePrimitive::Rect(
4487 PaintRect::solid(UiRect::new(8.0, 12.0, 108.0, 46.0), color(48, 112, 184))
4488 .stroke(AlignedStroke::inside(StrokeStyle::new(
4489 color(132, 174, 222),
4490 1.0,
4491 )))
4492 .corner_radii(CornerRadii::uniform(6.0)),
4493 ),
4494 ScenePrimitive::Circle {
4495 center: UiPoint::new(150.0, 35.0),
4496 radius: 22.0,
4497 fill: color(111, 203, 159),
4498 stroke: Some(StrokeStyle::new(color(176, 236, 206), 1.0)),
4499 },
4500 ScenePrimitive::Line {
4501 from: UiPoint::new(188.0, 18.0),
4502 to: UiPoint::new(238.0, 52.0),
4503 stroke: StrokeStyle::new(color(232, 186, 88), 3.0),
4504 },
4505 ],
4506 widgets::SceneOptions::default()
4507 .with_layout(LayoutStyle::new().with_width(260.0).with_height(70.0))
4508 .accessibility_label("Scene primitives"),
4509 );
4510
4511 let panel_shell = widgets::frame(
4512 ui,
4513 body,
4514 "containers.panels",
4515 widgets::FrameOptions::default().with_layout(
4516 LayoutStyle::column()
4517 .with_width_percent(1.0)
4518 .with_height(160.0)
4519 .with_padding(0.0)
4520 .with_gap(0.0),
4521 ),
4522 );
4523 let top = widgets::top_panel(ui, panel_shell, "containers.panels.top", 28.0);
4524 widgets::label(
4525 ui,
4526 top,
4527 "containers.panels.top.label",
4528 "Top panel",
4529 text(12.0, color(220, 228, 238)),
4530 LayoutStyle::new().with_width_percent(1.0),
4531 );
4532 let middle = row(ui, panel_shell, "containers.panels.middle", 0.0);
4533 let left = widgets::side_panel(
4534 ui,
4535 middle,
4536 "containers.panels.side",
4537 widgets::SidePanelSide::Left,
4538 90.0,
4539 );
4540 widgets::label(
4541 ui,
4542 left,
4543 "containers.panels.side.label",
4544 "Side",
4545 text(12.0, color(220, 228, 238)),
4546 LayoutStyle::new().with_width_percent(1.0),
4547 );
4548 let left = widgets::left_panel(ui, middle, "containers.panels.left", 90.0);
4549 widgets::label(
4550 ui,
4551 left,
4552 "containers.panels.left.label",
4553 "Left",
4554 text(12.0, color(220, 228, 238)),
4555 LayoutStyle::new().with_width_percent(1.0),
4556 );
4557 let center = widgets::central_panel(ui, middle, "containers.panels.center");
4558 widgets::label(
4559 ui,
4560 center,
4561 "containers.panels.center.label",
4562 "Central panel",
4563 text(12.0, color(220, 228, 238)),
4564 LayoutStyle::new().with_width_percent(1.0),
4565 );
4566 let right = widgets::right_panel(ui, middle, "containers.panels.right", 110.0);
4567 widgets::label(
4568 ui,
4569 right,
4570 "containers.panels.right.label",
4571 "Right",
4572 text(12.0, color(220, 228, 238)),
4573 LayoutStyle::new().with_width_percent(1.0),
4574 );
4575 let bottom = widgets::bottom_panel(ui, panel_shell, "containers.panels.bottom", 28.0);
4576 widgets::label(
4577 ui,
4578 bottom,
4579 "containers.panels.bottom.label",
4580 "Bottom panel",
4581 text(12.0, color(220, 228, 238)),
4582 LayoutStyle::new().with_width_percent(1.0),
4583 );
4584
4585 let scroll = widgets::scroll_area_with_bars(
4586 ui,
4587 body,
4588 "containers.scroll_area_with_bars",
4589 state.containers_scroll,
4590 widgets::ScrollAreaWithBarsOptions::default()
4591 .with_axes(ScrollAxes::BOTH)
4592 .with_vertical_scrollbar(
4593 widgets::ScrollbarOptions::default()
4594 .with_action("containers.scroll_area_with_bars.vertical-scrollbar"),
4595 )
4596 .with_horizontal_scrollbar(
4597 widgets::ScrollbarOptions::default()
4598 .with_action("containers.scroll_area_with_bars.horizontal-scrollbar"),
4599 )
4600 .with_layout(LayoutStyle::column().with_width(300.0).with_height(116.0)),
4601 );
4602 ui.node_mut(scroll.viewport).action = Some("containers.scroll_area_with_bars.scroll".into());
4603 for index in 0..5 {
4604 widgets::label(
4605 ui,
4606 scroll.viewport,
4607 format!("containers.scroll_area_with_bars.row.{index}"),
4608 format!("Scrollable row {}", index + 1),
4609 text(12.0, color(200, 212, 228)),
4610 LayoutStyle::new()
4611 .with_width(420.0)
4612 .with_height(28.0)
4613 .with_flex_shrink(0.0),
4614 );
4615 }
4616
4617 let area_host = ui.add_child(
4618 body,
4619 UiNode::container(
4620 "containers.area.host",
4621 LayoutStyle::new()
4622 .with_width_percent(1.0)
4623 .with_height(82.0)
4624 .with_flex_shrink(0.0),
4625 )
4626 .with_visual(UiVisual::panel(
4627 color(17, 20, 25),
4628 Some(StrokeStyle::new(color(58, 68, 84), 1.0)),
4629 4.0,
4630 )),
4631 );
4632 widgets::area(
4633 ui,
4634 area_host,
4635 "containers.area",
4636 widgets::AreaOptions::new(UiRect::new(14.0, 14.0, 180.0, 44.0))
4637 .with_visual(UiVisual::panel(color(39, 72, 109), None, 4.0))
4638 .accessibility_label("Absolute positioned area"),
4639 |ui, area| {
4640 widgets::label(
4641 ui,
4642 area,
4643 "containers.area.label",
4644 "Area",
4645 text(12.0, color(238, 244, 252)),
4646 LayoutStyle::new().with_width_percent(1.0),
4647 );
4648 },
4649 );
4650}
4651
4652fn form_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4653 let body = section(ui, parent, "forms", "Forms");
4654 let section = widgets::form_section(
4655 ui,
4656 body,
4657 "forms.profile",
4658 Some("Profile".to_string()),
4659 widgets::FormSectionOptions::default().with_layout(
4660 LayoutStyle::column()
4661 .with_width_percent(1.0)
4662 .with_padding(12.0)
4663 .with_gap(10.0),
4664 ),
4665 );
4666 let name = widgets::form_row(
4667 ui,
4668 section.root,
4669 "forms.profile.name",
4670 widgets::FormRowOptions::default(),
4671 );
4672 widgets::field_label(
4673 ui,
4674 name,
4675 "forms.profile.name.label",
4676 "Name",
4677 widgets::FieldLabelOptions::default().required(),
4678 );
4679 widgets::field_help_text(
4680 ui,
4681 name,
4682 "forms.profile.name.help",
4683 "Shown in window titles and project lists.",
4684 widgets::FieldHelpOptions::default(),
4685 );
4686 form_text_field(ui, name, "forms.profile.name.input", "Ada Lovelace");
4687
4688 let email = widgets::form_row(
4689 ui,
4690 section.root,
4691 "forms.profile.email",
4692 widgets::FormRowOptions::default()
4693 .required()
4694 .invalid("Use a complete email address"),
4695 );
4696 widgets::field_label(
4697 ui,
4698 email,
4699 "forms.profile.email.label",
4700 "Email",
4701 widgets::FieldLabelOptions::default().required(),
4702 );
4703 widgets::field_validation_message(
4704 ui,
4705 email,
4706 "forms.profile.email.validation",
4707 ValidationMessage::error("Use a complete email address"),
4708 widgets::ValidationMessageOptions::default(),
4709 );
4710 form_text_field(ui, email, "forms.profile.email.input", "ada@");
4711
4712 let role = widgets::form_row(
4713 ui,
4714 section.root,
4715 "forms.profile.role",
4716 widgets::FormRowOptions::default(),
4717 );
4718 widgets::field_label(
4719 ui,
4720 role,
4721 "forms.profile.role.label",
4722 "Role",
4723 widgets::FieldLabelOptions::default(),
4724 );
4725 widgets::field_help_text(
4726 ui,
4727 role,
4728 "forms.profile.role.help",
4729 "Form rows compose labels, controls, help, and validation text.",
4730 widgets::FieldHelpOptions::default(),
4731 );
4732 form_text_field(ui, role, "forms.profile.role.input", "Maintainer");
4733
4734 widgets::form_error_summary(
4735 ui,
4736 section.root,
4737 "forms.profile.errors",
4738 &state.form,
4739 widgets::FormErrorSummaryOptions::default(),
4740 );
4741 widgets::form_action_buttons(
4742 ui,
4743 section.root,
4744 "forms.profile.actions",
4745 &state.form,
4746 widgets::FormActionButtonsOptions::default()
4747 .include_reset(true)
4748 .with_action_prefix("forms.profile"),
4749 );
4750 widgets::label(
4751 ui,
4752 section.root,
4753 "forms.profile.status",
4754 format!("Status: {}", state.form_status),
4755 text(11.0, color(154, 166, 184)),
4756 LayoutStyle::new().with_width_percent(1.0),
4757 );
4758}
4759
4760fn overlay_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4761 let body = section(ui, parent, "overlays", "Overlays");
4762 let header = widgets::collapsing_header(
4763 ui,
4764 body,
4765 "overlays.collapsing",
4766 "Collapsing header",
4767 widgets::CollapsingHeaderOptions::default()
4768 .expanded(state.overlay_expanded)
4769 .with_toggle_action("overlays.collapsing.toggle"),
4770 );
4771 if let Some(panel) = header.body {
4772 widgets::label(
4773 ui,
4774 panel,
4775 "overlays.collapsing.body",
4776 "Expanded content lives under the header.",
4777 text(12.0, color(196, 210, 230)),
4778 LayoutStyle::new().with_width_percent(1.0),
4779 );
4780 }
4781
4782 let controls = row(ui, body, "overlays.controls", 8.0);
4783 button(
4784 ui,
4785 controls,
4786 "overlays.popup.toggle",
4787 if state.overlay_popup_open {
4788 "Close popup"
4789 } else {
4790 "Open popup"
4791 },
4792 "overlays.popup.toggle",
4793 button_visual(48, 112, 184),
4794 );
4795 button(
4796 ui,
4797 controls,
4798 "overlays.modal.open",
4799 "Open modal",
4800 "overlays.modal.open",
4801 button_visual(58, 78, 96),
4802 );
4803
4804 let tooltip = TooltipContent::new("Tooltip")
4805 .body("Tooltip boxes are overlay surfaces with title, body, and shortcut text.")
4806 .shortcut_label("Ctrl+K");
4807 let mut tooltip_options = widgets::TooltipBoxOptions::default()
4808 .with_layout(
4809 LayoutStyle::column()
4810 .with_width(280.0)
4811 .with_padding(8.0)
4812 .with_gap(4.0),
4813 )
4814 .with_animation(None);
4815 tooltip_options.layer = UiLayer::AppContent;
4816 tooltip_options.z_index = 0;
4817 widgets::tooltip_box(ui, body, "overlays.tooltip", tooltip, tooltip_options);
4818
4819 if state.overlay_popup_open {
4820 let popup = widgets::popup_panel(
4821 ui,
4822 parent,
4823 "overlays.popup_panel",
4824 UiRect::new(0.0, 20.0, 160.0, 96.0),
4825 widgets::PopupOptions {
4826 z_index: 20,
4827 accessibility: Some(
4828 AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup"),
4829 ),
4830 ..Default::default()
4831 },
4832 );
4833 let popup_body = ui.add_child(
4834 popup,
4835 UiNode::container(
4836 "overlays.popup_panel.body",
4837 LayoutStyle::column()
4838 .with_width_percent(1.0)
4839 .with_height_percent(1.0)
4840 .with_padding(10.0)
4841 .with_gap(6.0),
4842 ),
4843 );
4844 let popup_header = row(ui, popup_body, "overlays.popup_panel.header", 8.0);
4845 widgets::label(
4846 ui,
4847 popup_header,
4848 "overlays.popup_panel.label",
4849 "Popup panel",
4850 text(12.0, color(220, 228, 238)),
4851 LayoutStyle::new().with_width_percent(1.0),
4852 );
4853 let mut close = widgets::ButtonOptions::new(LayoutStyle::size(26.0, 22.0))
4854 .with_action("overlays.popup.close");
4855 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
4856 close.hovered_visual = Some(button_visual(54, 70, 92));
4857 close.text_style = text(12.0, color(220, 228, 238));
4858 widgets::button(ui, popup_header, "overlays.popup_panel.close", "x", close);
4859 widgets::label(
4860 ui,
4861 popup_body,
4862 "overlays.popup_panel.body_text",
4863 "Popup content is conditionally rendered.",
4864 text(11.0, color(196, 210, 230)),
4865 LayoutStyle::new().with_width_percent(1.0),
4866 );
4867 }
4868
4869 if state.overlay_modal_open {
4870 let modal = widgets::modal_dialog(
4871 ui,
4872 body,
4873 "overlays.modal",
4874 "Modal dialog",
4875 widgets::ModalDialogOptions::default()
4876 .with_size(320.0, 180.0)
4877 .with_close_action("overlays.modal.close")
4878 .with_dismissal(widgets::DialogDismissal::STANDARD)
4879 .with_focus_restore(FocusRestoreTarget::Previous)
4880 .modeless(),
4881 );
4882 widgets::label(
4883 ui,
4884 modal.body,
4885 "overlays.modal.body.text",
4886 "Dialog body",
4887 text(12.0, color(220, 228, 238)),
4888 LayoutStyle::new().with_width_percent(1.0),
4889 );
4890 }
4891}
4892
4893fn drag_drop_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
4894 let body = section(ui, parent, "drag_drop", "Drag and drop");
4895 widgets::dnd_drag_source(
4896 ui,
4897 body,
4898 "drag_drop.text_source",
4899 "Drag text payload",
4900 DragPayload::text("Operad payload"),
4901 widgets::DragSourceOptions::default()
4902 .with_kind(DragDropSurfaceKind::ListRow)
4903 .with_action("drag_drop.text_source")
4904 .with_accessibility_hint("Start a text drag operation"),
4905 );
4906
4907 let accepted_options = widgets::DropZoneOptions::default()
4908 .with_kind(DragDropSurfaceKind::EditorSurface)
4909 .with_accepted_payload(DropPayloadFilter::empty().text())
4910 .with_action("drag_drop.accept_text")
4911 .with_accessibility_hint("Accepts text payloads");
4912 let accepted = widgets::dnd_drop_zone(
4913 ui,
4914 body,
4915 "drag_drop.accept_text",
4916 "Drop text here",
4917 accepted_options.clone(),
4918 );
4919 widgets::dnd_apply_drop_zone_preview(
4920 ui,
4921 accepted.root,
4922 &accepted_options,
4923 widgets::DropZonePreviewState::Accepted,
4924 );
4925
4926 let rejected_options = widgets::DropZoneOptions::default()
4927 .with_layout(
4928 LayoutStyle::column()
4929 .with_width(240.0)
4930 .with_height(82.0)
4931 .with_padding(12.0),
4932 )
4933 .with_kind(DragDropSurfaceKind::Asset)
4934 .with_accepted_payload(DropPayloadFilter::empty().files())
4935 .with_action("drag_drop.files_only");
4936 let rejected = widgets::dnd_drop_zone(
4937 ui,
4938 body,
4939 "drag_drop.files_only",
4940 "Files only",
4941 rejected_options.clone(),
4942 );
4943 widgets::dnd_apply_drop_zone_preview(
4944 ui,
4945 rejected.root,
4946 &rejected_options,
4947 widgets::DropZonePreviewState::Rejected,
4948 );
4949 widgets::label(
4950 ui,
4951 body,
4952 "drag_drop.status",
4953 format!("Status: {}", state.drag_drop_status),
4954 text(11.0, color(154, 166, 184)),
4955 LayoutStyle::new().with_width_percent(1.0),
4956 );
4957}
4958
4959fn media_widgets(ui: &mut UiDocument, parent: UiNodeId) {
4960 let body = section(ui, parent, "media", "Media");
4961 let icons = row(ui, body, "media.icons", 10.0);
4962 widgets::image(
4963 ui,
4964 icons,
4965 "media.image.play",
4966 icon_image(BuiltInIcon::Play),
4967 widgets::ImageOptions::default()
4968 .with_layout(LayoutStyle::size(42.0, 42.0))
4969 .with_accessibility_label("Play icon"),
4970 );
4971 widgets::image(
4972 ui,
4973 icons,
4974 "media.image.warning",
4975 ImageContent::new(BuiltInIcon::Warning.key()).tinted(color(232, 186, 88)),
4976 widgets::ImageOptions::default()
4977 .with_layout(LayoutStyle::size(42.0, 42.0))
4978 .with_accessibility_label("Warning icon"),
4979 );
4980 widgets::image(
4981 ui,
4982 icons,
4983 "media.image.info",
4984 ImageContent::new(BuiltInIcon::Info.key()).tinted(color(118, 183, 255)),
4985 widgets::ImageOptions::default()
4986 .with_layout(LayoutStyle::size(42.0, 42.0))
4987 .with_accessibility_label("Info icon"),
4988 );
4989 widgets::label(
4990 ui,
4991 body,
4992 "media.image.note",
4993 "Image widgets reference stable resource keys; the host resolves them to textures or vector assets.",
4994 text(12.0, color(166, 176, 190)),
4995 LayoutStyle::new().with_width_percent(1.0),
4996 );
4997}
4998
4999fn timeline_ruler(ui: &mut UiDocument, parent: UiNodeId) {
5000 let mut layout = LayoutStyle::column()
5001 .with_width_percent(1.0)
5002 .with_height(40.0)
5003 .with_flex_shrink(0.0);
5004 layout.as_taffy_style_mut().min_size.width = operad::length(0.0);
5005 layout.as_taffy_style_mut().min_size.height = operad::length(0.0);
5006 let body = widgets::scroll_area(ui, parent, "timeline", ScrollAxes::BOTH, layout);
5007 widgets::timeline_ruler(
5008 ui,
5009 body,
5010 "timeline.ruler",
5011 widgets::RulerSpec {
5012 range: widgets::TimelineRange::new(0.0, 12.0),
5013 width: 600.0,
5014 major_step: 2.0,
5015 minor_step: 0.5,
5016 label_every: 1,
5017 },
5018 widgets::TimelineRulerOptions::default(),
5019 );
5020}
5021
5022fn toast_controls(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5023 let body = section(ui, parent, "toasts", "Toasts");
5024 let controls = row(ui, body, "toasts.controls", 10.0);
5025 button(
5026 ui,
5027 controls,
5028 "toasts.show",
5029 "Show toast",
5030 "toast.show",
5031 button_visual(48, 112, 184),
5032 );
5033 button(
5034 ui,
5035 controls,
5036 "toasts.hide",
5037 "Hide",
5038 "toast.hide",
5039 button_visual(58, 78, 96),
5040 );
5041 widgets::label(
5042 ui,
5043 body,
5044 "toasts.status",
5045 if state.toast_visible {
5046 "Toast overlay is visible."
5047 } else {
5048 "Toast overlay is hidden."
5049 },
5050 text(12.0, color(196, 210, 230)),
5051 LayoutStyle::new().with_width_percent(1.0),
5052 );
5053 widgets::label(
5054 ui,
5055 body,
5056 "toasts.action_status",
5057 format!("Action: {}", state.toast_action_status),
5058 text(12.0, color(154, 166, 184)),
5059 LayoutStyle::new().with_width_percent(1.0),
5060 );
5061}
5062
5063fn popup_controls(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5064 let body = section(ui, parent, "popup_panel", "Popup panel");
5065 let controls = row(ui, body, "popup_panel.controls", 8.0);
5066 button(
5067 ui,
5068 controls,
5069 "popup_panel.toggle",
5070 if state.popup_open {
5071 "Close popup"
5072 } else {
5073 "Open popup"
5074 },
5075 "popup.toggle",
5076 button_visual(48, 112, 184),
5077 );
5078 if state.popup_open {
5079 let mut close =
5080 widgets::ButtonOptions::new(LayoutStyle::size(30.0, 30.0)).with_action("popup.close");
5081 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
5082 close.hovered_visual = Some(button_visual(54, 70, 92));
5083 close.text_style = text(13.0, color(220, 228, 238));
5084 widgets::button(ui, controls, "popup_panel.inline_close", "x", close);
5085 }
5086 widgets::label(
5087 ui,
5088 body,
5089 "popup_panel.status",
5090 if state.popup_open {
5091 "Popup overlay is open."
5092 } else {
5093 "Popup overlay is closed."
5094 },
5095 text(12.0, color(196, 210, 230)),
5096 LayoutStyle::new().with_width_percent(1.0),
5097 );
5098 if state.popup_open {
5099 let panel = widgets::popup_panel(
5100 ui,
5101 parent,
5102 "popup_panel.inline_preview",
5103 UiRect::new(0.0, 20.0, 160.0, 104.0),
5104 widgets::PopupOptions {
5105 z_index: 4,
5106 accessibility: Some(
5107 AccessibilityMeta::new(AccessibilityRole::Dialog).label("Popup preview"),
5108 ),
5109 ..Default::default()
5110 },
5111 );
5112 let content = ui.add_child(
5113 panel,
5114 UiNode::container(
5115 "popup_panel.inline_preview.body",
5116 LayoutStyle::column()
5117 .with_width_percent(1.0)
5118 .with_height_percent(1.0)
5119 .with_padding(10.0)
5120 .with_gap(8.0),
5121 ),
5122 );
5123 let header = row(ui, content, "popup_panel.inline_preview.header", 8.0);
5124 widgets::label(
5125 ui,
5126 header,
5127 "popup_panel.inline_preview.title",
5128 "Popup panel",
5129 text(12.0, color(226, 234, 246)),
5130 LayoutStyle::new().with_width_percent(1.0),
5131 );
5132 let mut close =
5133 widgets::ButtonOptions::new(LayoutStyle::size(26.0, 22.0)).with_action("popup.close");
5134 close.visual = UiVisual::panel(color(28, 34, 43), None, 3.0);
5135 close.hovered_visual = Some(button_visual(54, 70, 92));
5136 close.text_style = text(12.0, color(220, 228, 238));
5137 widgets::button(ui, header, "popup_panel.inline_preview.close", "x", close);
5138 widgets::label(
5139 ui,
5140 content,
5141 "popup_panel.inline_preview.text",
5142 "Overlay content",
5143 text(11.0, color(196, 210, 230)),
5144 LayoutStyle::new().with_width_percent(1.0),
5145 );
5146 widgets::spacer(
5147 ui,
5148 body,
5149 "popup_panel.inline_preview.space",
5150 LayoutStyle::new()
5151 .with_width_percent(1.0)
5152 .with_height(112.0)
5153 .with_flex_shrink(0.0),
5154 );
5155 }
5156}
5157
5158fn styling_widgets(ui: &mut UiDocument, parent: UiNodeId, state: &ShowcaseState) {
5159 let body = section(ui, parent, "styling", "Styling");
5160 let grid = ui.add_child(
5161 body,
5162 UiNode::container(
5163 "styling.grid",
5164 LayoutStyle::row()
5165 .with_width_percent(1.0)
5166 .with_height_percent(1.0)
5167 .gap(16.0),
5168 ),
5169 );
5170 let controls = ui.add_child(
5171 grid,
5172 UiNode::container(
5173 "styling.controls",
5174 LayoutStyle::column()
5175 .with_width(330.0)
5176 .with_height_percent(1.0)
5177 .with_flex_shrink(0.0)
5178 .gap(6.0),
5179 ),
5180 );
5181 style_checkbox(
5182 ui,
5183 controls,
5184 "styling.inner_same",
5185 "Inner margin same",
5186 state.styling.inner_same,
5187 );
5188 style_slider(
5189 ui,
5190 controls,
5191 "styling.inner",
5192 "Inner left",
5193 state.styling.inner_margin,
5194 0.0..32.0,
5195 );
5196 if !state.styling.inner_same {
5197 style_slider(
5198 ui,
5199 controls,
5200 "styling.inner_right",
5201 "Inner right",
5202 state.styling.inner_right,
5203 0.0..32.0,
5204 );
5205 style_slider(
5206 ui,
5207 controls,
5208 "styling.inner_top",
5209 "Inner top",
5210 state.styling.inner_top,
5211 0.0..32.0,
5212 );
5213 style_slider(
5214 ui,
5215 controls,
5216 "styling.inner_bottom",
5217 "Inner bottom",
5218 state.styling.inner_bottom,
5219 0.0..32.0,
5220 );
5221 }
5222 style_checkbox(
5223 ui,
5224 controls,
5225 "styling.outer_same",
5226 "Outer margin same",
5227 state.styling.outer_same,
5228 );
5229 style_slider(
5230 ui,
5231 controls,
5232 "styling.outer",
5233 "Outer left",
5234 state.styling.outer_margin,
5235 0.0..40.0,
5236 );
5237 if !state.styling.outer_same {
5238 style_slider(
5239 ui,
5240 controls,
5241 "styling.outer_right",
5242 "Outer right",
5243 state.styling.outer_right,
5244 0.0..40.0,
5245 );
5246 style_slider(
5247 ui,
5248 controls,
5249 "styling.outer_top",
5250 "Outer top",
5251 state.styling.outer_top,
5252 0.0..40.0,
5253 );
5254 style_slider(
5255 ui,
5256 controls,
5257 "styling.outer_bottom",
5258 "Outer bottom",
5259 state.styling.outer_bottom,
5260 0.0..40.0,
5261 );
5262 }
5263 style_checkbox(
5264 ui,
5265 controls,
5266 "styling.radius_same",
5267 "Corner radius same",
5268 state.styling.radius_same,
5269 );
5270 style_slider(
5271 ui,
5272 controls,
5273 "styling.radius",
5274 "Radius NW",
5275 state.styling.corner_radius,
5276 0.0..28.0,
5277 );
5278 if !state.styling.radius_same {
5279 style_slider(
5280 ui,
5281 controls,
5282 "styling.radius_ne",
5283 "Radius NE",
5284 state.styling.corner_ne,
5285 0.0..28.0,
5286 );
5287 style_slider(
5288 ui,
5289 controls,
5290 "styling.radius_sw",
5291 "Radius SW",
5292 state.styling.corner_sw,
5293 0.0..28.0,
5294 );
5295 style_slider(
5296 ui,
5297 controls,
5298 "styling.radius_se",
5299 "Radius SE",
5300 state.styling.corner_se,
5301 0.0..28.0,
5302 );
5303 }
5304 style_slider(
5305 ui,
5306 controls,
5307 "styling.shadow_x",
5308 "Shadow x",
5309 state.styling.shadow_x,
5310 -24.0..24.0,
5311 );
5312 style_slider(
5313 ui,
5314 controls,
5315 "styling.shadow_y",
5316 "Shadow y",
5317 state.styling.shadow_y,
5318 -24.0..24.0,
5319 );
5320 style_slider(
5321 ui,
5322 controls,
5323 "styling.shadow",
5324 "Shadow blur",
5325 state.styling.shadow_blur,
5326 0.0..32.0,
5327 );
5328 style_slider(
5329 ui,
5330 controls,
5331 "styling.shadow_spread",
5332 "Shadow spread",
5333 state.styling.shadow_spread,
5334 0.0..16.0,
5335 );
5336 style_slider(
5337 ui,
5338 controls,
5339 "styling.shadow_alpha",
5340 "Shadow color",
5341 state.styling.shadow_alpha,
5342 0.0..220.0,
5343 );
5344 style_slider(
5345 ui,
5346 controls,
5347 "styling.fill",
5348 "Fill color",
5349 state.styling.fill_tint,
5350 0.0..1.0,
5351 );
5352 style_slider(
5353 ui,
5354 controls,
5355 "styling.stroke_color",
5356 "Stroke color",
5357 state.styling.stroke_tint,
5358 0.0..1.0,
5359 );
5360 style_slider(
5361 ui,
5362 controls,
5363 "styling.stroke",
5364 "Stroke",
5365 state.styling.stroke_width,
5366 0.0..4.0,
5367 );
5368
5369 let preview = ui.add_child(
5370 grid,
5371 UiNode::container(
5372 "styling.preview",
5373 LayoutStyle::column()
5374 .with_width_percent(1.0)
5375 .with_height_percent(1.0)
5376 .padding(8.0),
5377 )
5378 .with_visual(UiVisual::panel(
5379 color(17, 20, 25),
5380 Some(StrokeStyle::new(color(56, 66, 82), 1.0)),
5381 4.0,
5382 )),
5383 );
5384 style_preview(ui, preview, state.styling);
5385}
5386
5387fn style_slider(
5388 ui: &mut UiDocument,
5389 parent: UiNodeId,
5390 name: &'static str,
5391 label: &'static str,
5392 value: f32,
5393 range: std::ops::Range<f32>,
5394) {
5395 let row = row(ui, parent, format!("{name}.row"), 8.0);
5396 widgets::label(
5397 ui,
5398 row,
5399 format!("{name}.label"),
5400 label,
5401 text(12.0, color(166, 176, 190)),
5402 LayoutStyle::new().with_width(118.0),
5403 );
5404 widgets::label(
5405 ui,
5406 row,
5407 format!("{name}.value"),
5408 if range.end <= 1.0 {
5409 format!("{value:.2}")
5410 } else {
5411 format!("{value:.0}")
5412 },
5413 text(12.0, color(226, 232, 242)),
5414 LayoutStyle::new().with_width(42.0),
5415 );
5416 let mut options = widgets::SliderOptions::default()
5417 .with_layout(
5418 LayoutStyle::new()
5419 .with_width(112.0)
5420 .with_height(20.0)
5421 .with_flex_shrink(0.0),
5422 )
5423 .with_value_edit_action(name);
5424 options.fill_color = color(120, 170, 230);
5425 widgets::slider(
5426 ui,
5427 row,
5428 format!("{name}.slider"),
5429 ((value - range.start) / (range.end - range.start).max(f32::EPSILON)).clamp(0.0, 1.0),
5430 0.0..1.0,
5431 options,
5432 );
5433}
5434
5435fn style_checkbox(
5436 ui: &mut UiDocument,
5437 parent: UiNodeId,
5438 name: &'static str,
5439 label: &'static str,
5440 checked: bool,
5441) {
5442 let mut options = widgets::CheckboxOptions::default().with_action(name);
5443 options.layout = LayoutStyle::new().with_width_percent(1.0).with_height(22.0);
5444 options.text_style = text(12.0, color(220, 228, 238));
5445 widgets::checkbox(ui, parent, name, label, checked, options);
5446}
5447
5448fn style_preview(ui: &mut UiDocument, parent: UiNodeId, styling: StylingState) {
5449 let outer = styling.outer_edges();
5450 let inner = styling.inner_edges();
5451 let frame = UiRect::new(
5452 22.0 + outer[0],
5453 28.0 + outer[2],
5454 108.0 + inner[0] + inner[1],
5455 40.0 + inner[2] + inner[3],
5456 );
5457 let text_rect = UiRect::new(
5458 frame.x + inner[0],
5459 frame.y + inner[2],
5460 (frame.width - inner[0] - inner[1]).max(1.0),
5461 (frame.height - inner[2] - inner[3]).max(1.0),
5462 );
5463 ui.add_child(
5464 parent,
5465 UiNode::scene(
5466 "styling.preview.scene",
5467 vec![
5468 ScenePrimitive::Rect(
5469 PaintRect::solid(frame, styling.fill_color())
5470 .stroke(AlignedStroke::inside(StrokeStyle::new(
5471 styling.stroke_color(),
5472 styling.stroke_width,
5473 )))
5474 .corner_radii(styling.radii())
5475 .effect(PaintEffect::shadow(
5476 styling.shadow_color(),
5477 UiPoint::new(styling.shadow_x, styling.shadow_y),
5478 styling.shadow_blur,
5479 styling.shadow_spread,
5480 )),
5481 ),
5482 ScenePrimitive::Text(
5483 PaintText::new("Content", text_rect, text(13.0, color(255, 255, 255)))
5484 .horizontal_align(TextHorizontalAlign::Center)
5485 .vertical_align(TextVerticalAlign::Center)
5486 .multiline(false),
5487 ),
5488 ],
5489 LayoutStyle::new()
5490 .with_width_percent(1.0)
5491 .with_height(180.0)
5492 .with_flex_shrink(0.0),
5493 ),
5494 );
5495}pub fn right(self) -> f32
pub fn bottom(self) -> f32
pub fn contains_point(self, point: UiPoint) -> bool
pub fn intersects(self, other: UiRect) -> bool
pub fn contains_rect(self, other: UiRect) -> bool
pub fn intersection(self, other: UiRect) -> Option<UiRect>
Trait Implementations§
impl Copy for UiRect
impl StructuralPartialEq for UiRect
Auto Trait Implementations§
impl Freeze for UiRect
impl RefUnwindSafe for UiRect
impl Send for UiRect
impl Sync for UiRect
impl Unpin for UiRect
impl UnsafeUnpin for UiRect
impl UnwindSafe for UiRect
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.