Skip to main content

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