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#![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#[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 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#[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#[property(EVENT + 1, widget_impl(Menu, DefaultStyle))]
75pub fn has_open(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
76 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#[widget($crate::DefaultStyle)]
90pub struct DefaultStyle(Style);
91impl DefaultStyle {
92 fn widget_intrinsic(&mut self) {
93 widget_set! {
94 self;
95
96 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#[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#[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#[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#[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#[property(FILL)]
250pub fn icon(child: impl IntoUiNode, icon: impl IntoUiNode) -> UiNode {
251 sub::start_column(child, icon)
252}
253
254#[property(FILL)]
264pub fn icon_fn(child: impl IntoUiNode, icon: impl IntoVar<WidgetFn<()>>) -> UiNode {
265 sub::start_column_fn(child, icon)
266}
267
268#[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#[allow(non_camel_case_types)]
285pub struct MENU_TEXT_INPUT;
286impl MENU_TEXT_INPUT {
287 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 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) {}