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_style::{Style, StyleMix, impl_named_style_fn, impl_style_fn, style_fn};
21
22pub mod context;
23pub mod popup;
24pub mod sub;
25
26#[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 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#[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#[property(EVENT + 1, widget_impl(Menu, DefaultStyle))]
73pub fn has_open(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
74 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#[widget($crate::DefaultStyle)]
88pub struct DefaultStyle(Style);
89impl DefaultStyle {
90 fn widget_intrinsic(&mut self) {
91 widget_set! {
92 self;
93
94 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#[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#[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#[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#[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#[property(FILL)]
244pub fn icon(child: impl IntoUiNode, icon: impl IntoUiNode) -> UiNode {
245 sub::start_column(child, icon)
246}
247
248#[property(FILL)]
258pub fn icon_fn(child: impl IntoUiNode, icon: impl IntoVar<WidgetFn<()>>) -> UiNode {
259 sub::start_column_fn(child, icon)
260}
261
262#[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}