zng_wgt_menu/
lib.rs
1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/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_ext_font::FontNames;
16use zng_ext_input::{focus::FOCUS, mouse::ClickMode};
17use zng_ext_l10n::lang;
18use zng_wgt::{align, base_color, is_disabled, is_mobile, margin, prelude::*};
19use zng_wgt_access::{AccessRole, access_role};
20use zng_wgt_button::BUTTON;
21use zng_wgt_container::{child_align, child_end, padding};
22use zng_wgt_fill::{background_color, foreground_highlight};
23use zng_wgt_filter::{opacity, saturate};
24use zng_wgt_input::{CursorIcon, cursor, focus::alt_focus_scope};
25use zng_wgt_input::{click_mode, focus::is_focused, mouse::on_pre_mouse_enter};
26use zng_wgt_size_offset::size;
27use zng_wgt_style::{Style, StyleMix, impl_style_fn, style_fn};
28use zng_wgt_text::Text;
29
30pub mod context;
31pub mod popup;
32pub mod sub;
33
34#[widget($crate::Menu {
36 ($children:expr) => {
37 children = $children;
38 }
39})]
40pub struct Menu(StyleMix<zng_wgt_panel::Panel>);
41impl Menu {
42 fn widget_intrinsic(&mut self) {
43 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
44 widget_set! {
45 self;
46 alt_focus_scope = true;
47 zng_wgt_panel::panel_fn = PANEL_FN_VAR;
48 style_base_fn = style_fn!(|_| DefaultStyle!());
49 access_role = AccessRole::Menu;
50 }
51 }
52}
53impl_style_fn!(Menu);
54
55context_var! {
56 pub static PANEL_FN_VAR: WidgetFn<zng_wgt_panel::PanelArgs> = wgt_fn!(|a: zng_wgt_panel::PanelArgs| {
63 zng_wgt_wrap::Wrap! {
64 children = a.children;
65 }
66 });
67
68 pub static SHORTCUT_SPACING_VAR: Length = 20;
77}
78
79#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(Menu))]
87pub fn panel_fn(child: impl UiNode, panel: impl IntoVar<WidgetFn<zng_wgt_panel::PanelArgs>>) -> impl UiNode {
88 with_context_var(child, PANEL_FN_VAR, panel)
89}
90
91#[widget($crate::DefaultStyle)]
97pub struct DefaultStyle(Style);
98impl DefaultStyle {
99 fn widget_intrinsic(&mut self) {
100 widget_set! {
101 self;
102
103 base_color = light_dark(rgb(0.82, 0.82, 0.82), rgb(0.18, 0.18, 0.18));
106 zng_wgt_button::style_fn = style_fn!(|_| ButtonStyle!());
107 zng_wgt_toggle::style_fn = style_fn!(|_| ToggleStyle!());
108 zng_wgt_rule_line::hr::color = BASE_COLOR_VAR.shade(1);
109 zng_wgt_text::icon::ico_size = 18;
110 }
111 }
112}
113
114#[widget($crate::ButtonStyle)]
121pub struct ButtonStyle(Style);
122impl ButtonStyle {
123 fn widget_intrinsic(&mut self) {
124 widget_set! {
125 self;
126 replace = true;
127
128 sub::column_width_padding = true;
129 padding = (4, 0);
130 child_align = Align::START;
131
132 base_color = light_dark(rgb(0.82, 0.82, 0.82), rgb(0.18, 0.18, 0.18));
133 background_color = BASE_COLOR_VAR.rgba();
134 opacity = 90.pct();
135 foreground_highlight = unset!;
136 zng_wgt_tooltip::tooltip_fn = WidgetFn::nil(); click_mode = ClickMode::release();access_role = AccessRole::MenuItem;
141
142 on_pre_mouse_enter = hn!(|_| {
143 FOCUS.focus_widget(WIDGET.id(), false);
144 });
145
146 shortcut_txt = Text! {
147 txt = BUTTON.cmd().flat_map(|c| match c {
148 Some(c) => c.shortcut_txt(),
149 None => LocalVar(Txt::from("")).boxed()
150 });
151 align = Align::CENTER;
152 };
153
154 icon_fn = BUTTON.cmd().flat_map(|c| match c {
155 Some(c) => c.icon().boxed(),
156 None => LocalVar(WidgetFn::nil()).boxed()
157 });
158
159 when *#is_focused {
160 background_color = BASE_COLOR_VAR.shade(1);
161 opacity = 100.pct();
162 }
163
164 when *#is_disabled {
165 saturate = false;
166 opacity = 50.pct();
167 cursor = CursorIcon::NotAllowed;
168 }
169
170 when *#is_mobile {
171 shortcut_txt = NilUiNode;
172 }
173 }
174 }
175}
176
177#[widget($crate::TouchButtonStyle)]
182pub struct TouchButtonStyle(Style);
183impl TouchButtonStyle {
184 fn widget_intrinsic(&mut self) {
185 widget_set! {
186 self;
187 zng_wgt::corner_radius = 0;
188 zng_wgt::visibility = BUTTON
189 .cmd()
190 .flat_map(|c| match c {
191 Some(c) => c.is_enabled().boxed(),
192 None => LocalVar(true).boxed(),
193 })
194 .map_into();
195 }
196 }
197}
198
199#[widget($crate::ToggleStyle)]
206pub struct ToggleStyle(ButtonStyle);
207impl ToggleStyle {
208 fn widget_intrinsic(&mut self) {
209 widget_set! {
210 self;
211 replace = true;
212
213 click_mode = ClickMode::release();
214 access_role = AccessRole::MenuItemCheckBox;
215
216 sub::start_column_fn = wgt_fn!(|_| Text! {
217 size = 1.2.em();
218 font_family = FontNames::system_ui(&lang!(und));
219 align = Align::CENTER;
220
221 txt = "✓";
222 when #{zng_wgt_toggle::IS_CHECKED_VAR}.is_none() {
223 txt = "━";
224 }
225
226 font_color = zng_wgt_text::FONT_COLOR_VAR.map(|c| c.transparent());
227 when #{zng_wgt_toggle::IS_CHECKED_VAR}.unwrap_or(true) {
228 font_color = zng_wgt_text::FONT_COLOR_VAR;
229 }
230 })
231 }
232 }
233}
234
235#[property(FILL)]
245pub fn icon(child: impl UiNode, icon: impl UiNode) -> impl UiNode {
246 sub::start_column(child, icon)
247}
248
249#[property(FILL)]
259pub fn icon_fn(child: impl UiNode, icon: impl IntoVar<WidgetFn<()>>) -> impl UiNode {
260 sub::start_column_fn(child, icon)
261}
262
263#[property(CHILD_CONTEXT)]
271pub fn shortcut_txt(child: impl UiNode, shortcut: impl UiNode) -> impl UiNode {
272 let shortcut = margin(shortcut, sub::END_COLUMN_WIDTH_VAR.map(|w| SideOffsets::new(0, w.clone(), 0, 0)));
273 child_end(child, shortcut, SHORTCUT_SPACING_VAR)
274}
275
276#[property(CONTEXT, default(SHORTCUT_SPACING_VAR))]
285pub fn shortcut_spacing(child: impl UiNode, spacing: impl IntoVar<Length>) -> impl UiNode {
286 with_context_var(child, SHORTCUT_SPACING_VAR, spacing)
287}