zng_wgt_menu/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Menu widgets and properties.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use colors::BASE_COLOR_VAR;
15use zng_wgt::{base_color, margin, prelude::*};
16use zng_wgt_access::{AccessRole, access_role};
17use zng_wgt_button::BUTTON;
18use zng_wgt_container::{child_end, padding};
19use zng_wgt_input::focus::{FocusClickBehavior, alt_focus_scope, focus_click_behavior};
20use zng_wgt_style::{Style, StyleMix, impl_named_style_fn, impl_style_fn, style_fn};
21
22pub mod context;
23pub mod popup;
24pub mod sub;
25
26/// Menu root panel.
27#[widget($crate::Menu { ($children:expr) => { children = $children; } })]
28pub struct Menu(StyleMix<zng_wgt_panel::Panel>);
29impl Menu {
30    fn widget_intrinsic(&mut self) {
31        self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
32        widget_set! {
33            self;
34            alt_focus_scope = true;
35            zng_wgt_panel::panel_fn = PANEL_FN_VAR;
36            access_role = AccessRole::Menu;
37            zng_wgt_rule_line::collapse_scope = true;
38        }
39    }
40}
41impl_style_fn!(Menu, DefaultStyle);
42
43context_var! {
44    /// Defines the layout widget for [`Menu!`].
45    ///
46    /// Is a [`Wrap!`] panel by default.
47    ///
48    /// [`Menu!`]: struct@Menu
49    /// [`Wrap!`]: struct@zng_wgt_wrap::Wrap
50    pub static PANEL_FN_VAR: WidgetFn<zng_wgt_panel::PanelArgs> = wgt_fn!(|a: zng_wgt_panel::PanelArgs| {
51        zng_wgt_wrap::Wrap! {
52            children = a.children;
53        }
54    });
55
56    static OPEN_SUBMENU_VAR: u32 = 0;
57}
58
59/// Widget function that generates the menu layout.
60///
61/// This property can be set in any widget to affect all [`Menu!`] descendants.
62///
63/// This property sets [`PANEL_FN_VAR`].
64///
65/// [`Menu!`]: struct@Menu
66#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(Menu, DefaultStyle))]
67pub fn panel_fn(child: impl IntoUiNode, panel: impl IntoVar<WidgetFn<zng_wgt_panel::PanelArgs>>) -> UiNode {
68    with_context_var(child, PANEL_FN_VAR, panel)
69}
70
71/// Gets if any descendant sub-menu is open.
72#[property(EVENT + 1, widget_impl(Menu, DefaultStyle))]
73pub fn has_open(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
74    // EVENT+1 to clear the `sub_menu_node` in case this is set in a sub-menu that
75    // sub-menu will see the parent OPEN_SUBMENU_VAR for setting its own state
76
77    let raw_state = var(0u32);
78    let state = state.into_var();
79    raw_state.bind_map(&state, |&v| v > 0).perm();
80    raw_state.hold(state).perm();
81    with_context_var(child, OPEN_SUBMENU_VAR, raw_state)
82}
83
84/// Default [`Menu!`] style.
85///
86/// [`Menu!`]: struct@Menu
87#[widget($crate::DefaultStyle)]
88pub struct DefaultStyle(Style);
89impl DefaultStyle {
90    fn widget_intrinsic(&mut self) {
91        widget_set! {
92            self;
93
94            // also see context::DefaultStyle
95
96            base_color = light_dark(rgb(0.82, 0.82, 0.82), rgb(0.18, 0.18, 0.18));
97            zng_wgt_button::style_fn = style_fn!(|_| ButtonStyle!());
98            zng_wgt_toggle::style_fn = style_fn!(|_| ToggleStyle!());
99            zng_wgt_toggle::combo_style_fn = style_fn!(|_| ComboStyle!());
100            zng_wgt_rule_line::hr::color = BASE_COLOR_VAR.shade(1);
101            zng_wgt_rule_line::vr::color = BASE_COLOR_VAR.shade(1);
102            zng_wgt_rule_line::vr::height = 1.em();
103        }
104    }
105}
106
107/// Style applied to all [`Button!`] widgets inside [`Menu!`] root.
108///
109/// Gives the button a *toolbar-item* look.
110///
111/// See also [`sub::ButtonStyle!`] for the style of buttons inside the sub-menus.
112///
113/// [`Button!`]: struct@zng_wgt_button::Button
114/// [`Menu!`]: struct@Menu
115/// [`sub::ButtonStyle!`]: struct@sub::ButtonStyle
116#[widget($crate::ButtonStyle)]
117pub struct ButtonStyle(zng_wgt_button::LightStyle);
118impl ButtonStyle {
119    fn widget_intrinsic(&mut self) {
120        widget_set! {
121            self;
122
123            padding = 4;
124
125            access_role = AccessRole::MenuItem;
126            focus_click_behavior = FocusClickBehavior::ExitEnabled;
127
128            zng_wgt_container::child_start =
129                BUTTON
130                    .cmd()
131                    .flat_map(|c| c.as_ref().map(|c| c.icon()).unwrap_or_else(|| const_var(WidgetFn::nil())))
132                    .present_data(()),
133            ;
134        }
135    }
136}
137
138/// Alternate style for buttons inside a menu.
139///
140/// If the button has a command, show the icon as child, if the command has no icon shows the name.
141///
142/// [`Button!`]: struct@zng_wgt_button::Button
143/// [`Menu!`]: struct@Menu
144/// [`sub::ButtonStyle!`]: struct@sub::ButtonStyle
145#[widget($crate::IconButtonStyle)]
146pub struct IconButtonStyle(zng_wgt_button::LightStyle);
147impl_named_style_fn!(icon_button, IconButtonStyle);
148impl IconButtonStyle {
149    fn widget_intrinsic(&mut self) {
150        widget_set! {
151            self;
152            named_style_fn = ICON_BUTTON_STYLE_FN_VAR;
153
154            padding = 4;
155
156            access_role = AccessRole::MenuItem;
157            focus_click_behavior = FocusClickBehavior::ExitEnabled;
158
159            zng_wgt_container::child =
160                BUTTON
161                    .cmd()
162                    .flat_map(|c| match c {
163                        Some(c) => expr_var! {
164                            let icon = #{c.icon()};
165                            let name = #{c.name()};
166                            wgt_fn!(icon, name, |args| {
167                                let icon = icon(args);
168                                if icon.is_nil() { zng_wgt_text::Text!(name.clone()) } else { icon }
169                            })
170                        },
171                        None => const_var(WidgetFn::nil()),
172                    })
173                    .present_data(()),
174            ;
175
176            zng_wgt_button::cmd_tooltip_fn = wgt_fn!(|args: zng_wgt_button::CmdTooltipArgs| {
177                let name = args.cmd.name();
178                let info = args.cmd.info();
179                let shortcut = args.cmd.shortcut();
180                zng_wgt_tooltip::Tip!(zng_wgt_stack::Stack! {
181                    direction = zng_wgt_stack::StackDirection::top_to_bottom();
182                    spacing = 5;
183                    children = ui_vec![
184                        zng_wgt_text::Text!(name),
185                        zng_wgt_text::Text! {
186                            zng_wgt::visibility = info.map(|s| (!s.is_empty()).into());
187                            txt = info;
188                        },
189                        zng_wgt_shortcut::ShortcutText!(shortcut)
190                    ];
191                })
192            });
193        }
194    }
195}
196
197/// Style applied to all [`Toggle!`] widgets inside [`Menu!`] root.
198///
199/// Gives the toggle a *toolbar-item* look.
200///
201/// [`Toggle!`]: struct@zng_wgt_toggle::Toggle
202/// [`Menu!`]: struct@Menu
203#[widget($crate::ToggleStyle)]
204pub struct ToggleStyle(zng_wgt_toggle::LightStyle);
205impl ToggleStyle {
206    fn widget_intrinsic(&mut self) {
207        widget_set! {
208            self;
209            padding = 4;
210            access_role = AccessRole::MenuItem;
211            focus_click_behavior = FocusClickBehavior::ExitEnabled;
212        }
213    }
214}
215
216/// Style applied to all [`Toggle!`] widgets using the [`toggle::ComboStyle!`] inside [`Menu!`] root.
217///
218/// Gives the toggle a *toolbar-item* look.
219///
220/// [`Toggle!`]: struct@zng_wgt_toggle::Toggle
221/// [`toggle::ComboStyle!`]: struct@zng_wgt_toggle::ComboStyle
222/// [`Menu!`]: struct@Menu
223#[widget($crate::ComboStyle)]
224pub struct ComboStyle(zng_wgt_toggle::ComboStyle);
225impl ComboStyle {
226    fn widget_intrinsic(&mut self) {
227        widget_set! {
228            self;
229            access_role = AccessRole::MenuItem;
230        }
231    }
232}
233
234/// Menu item icon.
235///
236/// Set on a [`Button!`] inside a sub-menu to define the menu [`Icon!`] for that button.
237///
238/// This property is an alias for [`sub::start_column`].
239///
240/// [`Button!`]: struct@zng_wgt_button::Button
241/// [`Icon!`]: struct@zng_wgt_text::icon::Icon
242/// [`sub::start_column`]: fn@sub::start_column
243#[property(FILL)]
244pub fn icon(child: impl IntoUiNode, icon: impl IntoUiNode) -> UiNode {
245    sub::start_column(child, icon)
246}
247
248/// Menu item icon from widget function.
249///
250/// Set on a [`Button!`] inside a sub-menu to define the menu [`Icon!`] for that button.
251///
252/// This property is an alias for [`sub::start_column_fn`].
253///
254/// [`Button!`]: struct@zng_wgt_button::Button
255/// [`Icon!`]: struct@zng_wgt_text::icon::Icon
256/// [`sub::start_column_fn`]: fn@sub::start_column_fn
257#[property(FILL)]
258pub fn icon_fn(child: impl IntoUiNode, icon: impl IntoVar<WidgetFn<()>>) -> UiNode {
259    sub::start_column_fn(child, icon)
260}
261
262/// Menu item shortcut text.
263///
264/// Set this on a [`Button!`] inside a sub-menu to define the shortcut text.
265///
266/// Note that this does not define the click shortcut, just the display of it. The [`ShortcutText!`]
267/// widget is recommended.
268///
269/// [`Button!`]: struct@zng_wgt_button::Button
270/// [`ShortcutText!`]: struct@zng_wgt_shortcut::ShortcutText
271#[property(CHILD_CONTEXT)]
272pub fn shortcut_txt(child: impl IntoUiNode, shortcut: impl IntoUiNode) -> UiNode {
273    let shortcut = margin(shortcut, sub::END_COLUMN_WIDTH_VAR.map(|w| SideOffsets::new(0, w.clone(), 0, 0)));
274    child_end(child, shortcut)
275}