Skip to main content

snora_widgets/
menu.rs

1//! Render a [`snora_core::Menu`] into an `iced::Element`.
2//!
3//! A menu renders as a column: the trigger button on top, and — if the
4//! menu is currently active — the item list below. The engine installs
5//! the click-outside backdrop at the application level, so this widget
6//! only paints the menu's own surface.
7
8use std::fmt::Debug;
9
10use iced::{
11    Alignment::Center,
12    Element,
13    widget::{button, column, container, row, text},
14};
15
16use snora_core::{Menu, MenuAction};
17
18use crate::style::menu_button_style;
19use crate::icon::icon_element;
20
21/// Render a single menu (header button + dropdown when active).
22///
23/// `on_action` is called for every interaction and must map the resulting
24/// [`MenuAction`] into the application's message type. A plain function
25/// item works well here because it has a zero-sized `'static` type and
26/// can be passed by reference.
27pub fn render_menu<'a, Message, MenuId, MenuItemId, F>(
28    menu: Menu<MenuId, MenuItemId>,
29    on_action: &'a F,
30    is_active: bool,
31) -> Element<'a, Message>
32where
33    Message: Clone + 'a,
34    MenuId: Clone + Debug + PartialEq + 'a,
35    MenuItemId: Clone + Debug + 'a,
36    F: Fn(MenuAction<MenuId, MenuItemId>) -> Message + 'a,
37{
38    // Trigger button (always visible).
39    let mut header_content = row![].spacing(6).align_y(Center);
40    if let Some(ref ic) = menu.icon {
41        header_content = header_content.push(icon_element(ic));
42    }
43    header_content = header_content.push(text(menu.label).size(14));
44
45    let trigger_msg = on_action(MenuAction::MenuPressed(menu.id.clone()));
46
47    let mut stack = column![];
48    stack = stack.push(
49        button(header_content)
50            .style(menu_button_style)
51            .on_press(trigger_msg),
52    );
53
54    if !is_active {
55        return container(stack).into();
56    }
57
58    // Dropdown — only rendered when this menu is active.
59    for item in menu.items {
60        let mut btn_content = row![].spacing(6).align_y(Center);
61        if let Some(ref ic) = item.icon {
62            btn_content = btn_content.push(icon_element(ic));
63        }
64        btn_content = btn_content.push(text(item.label).size(14));
65
66        let msg = on_action(MenuAction::MenuItemPressed {
67            menu_id: item.menu_id.clone(),
68            menu_item_id: item.id.clone(),
69        });
70
71        stack = stack.push(
72            button(btn_content)
73                .style(menu_button_style)
74                .on_press(msg),
75        );
76    }
77
78    container(stack).into()
79}