Skip to main content

UiRect

Struct UiRect 

Source
pub struct UiRect {
    pub x: f32,
    pub y: f32,
    pub width: f32,
    pub height: f32,
}

Fields§

§x: f32§y: f32§width: f32§height: f32

Implementations§

Source§

impl UiRect

Source

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}
Source

pub fn right(self) -> f32

Source

pub fn bottom(self) -> f32

Source

pub fn contains_point(self, point: UiPoint) -> bool

Source

pub fn intersects(self, other: UiRect) -> bool

Source

pub fn contains_rect(self, other: UiRect) -> bool

Source

pub fn intersection(self, other: UiRect) -> Option<UiRect>

Trait Implementations§

Source§

impl Clone for UiRect

Source§

fn clone(&self) -> UiRect

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for UiRect

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for UiRect

Source§

fn eq(&self, other: &UiRect) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for UiRect

Source§

impl StructuralPartialEq for UiRect

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Downcast<T> for T

Source§

fn downcast(&self) -> &T

Source§

impl<T> Downcast for T
where T: Any,

Source§

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>

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)

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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> Upcast<T> for T

Source§

fn upcast(&self) -> Option<&T>

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> WasmNotSend for T
where T: Send,

Source§

impl<T> WasmNotSendSync for T

Source§

impl<T> WasmNotSync for T
where T: Sync,