1//! Context menu widget and properties.
23use colors::BASE_COLOR_VAR;
4use zng_ext_input::gesture::CLICK_EVENT;
5use zng_wgt::prelude::*;
6use zng_wgt_input::focus::alt_focus_scope;
7use zng_wgt_layer::{
8 AnchorMode,
9 popup::{CONTEXT_CAPTURE_VAR, POPUP, PopupState},
10};
11use zng_wgt_stack::{Stack, StackDirection};
12use zng_wgt_style::{impl_style_fn, style_fn};
1314/// Defines the context menu shown when the widget is enabled and receives a context click.
15///
16/// The `menu` can be any widget, the [`ContextMenu!`] is recommended. The menu widget is open
17/// using [`POPUP`] and is expected to close itself when the context action is finished or it
18/// loses focus.
19///
20/// [`ContextMenu!`]: struct@ContextMenu
21/// [`POPUP`]: zng_wgt_layer::popup::POPUP
22#[property(EVENT)]
23pub fn context_menu(child: impl UiNode, menu: impl UiNode) -> impl UiNode {
24 context_menu_fn(child, WidgetFn::singleton(menu))
25}
2627/// Defines the context menu function shown when the widget is enabled and receives a context click.
28///
29/// The `menu` can return any widget, the [`ContextMenu!`] is recommended.
30///
31/// [`ContextMenu!`]: struct@ContextMenu
32#[property(EVENT, default(WidgetFn::nil()))]
33pub fn context_menu_fn(child: impl UiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>) -> impl UiNode {
34 context_menu_node(child, menu, false)
35}
3637/// Defines the context menu shown when the widget is disabled and receives a context click.
38///
39/// The `menu` can be any widget, the [`ContextMenu!`] is recommended.
40///
41/// [`ContextMenu!`]: struct@ContextMenu
42#[property(EVENT)]
43pub fn disabled_context_menu(child: impl UiNode, menu: impl UiNode) -> impl UiNode {
44 disabled_context_menu_fn(child, WidgetFn::singleton(menu))
45}
4647/// Defines the context menu function shown when the widget is disabled and receives a context click.
48///
49/// The `menu` can return any widget, the [`ContextMenu!`] is recommended.
50///
51/// [`ContextMenu!`]: struct@ContextMenu
52#[property(EVENT, default(WidgetFn::nil()))]
53pub fn disabled_context_menu_fn(child: impl UiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>) -> impl UiNode {
54 context_menu_node(child, menu, true)
55}
5657fn context_menu_node(child: impl UiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>, disabled_only: bool) -> impl UiNode {
58let menu = menu.into_var();
59let mut pop_state = var(PopupState::Closed).read_only();
6061 match_node(child, move |c, op| match op {
62 UiNodeOp::Init => {
63 WIDGET.sub_var(&menu).sub_event(&CLICK_EVENT);
64 }
65 UiNodeOp::Deinit => {
66 POPUP.close(&pop_state);
67 }
68 UiNodeOp::Event { update } => {
69 c.event(update);
70if let Some(args) = CLICK_EVENT.on_unhandled(update) {
71if args.is_context() {
72let apply = if disabled_only {
73 args.target.interactivity().is_disabled()
74 } else {
75 args.target.interactivity().is_enabled()
76 };
77if apply {
78 args.propagation().stop();
7980let menu = menu.get()(ContextMenuArgs {
81 anchor_id: WIDGET.id(),
82 disabled: disabled_only,
83 });
84let is_shortcut = args.is_from_keyboard();
85 pop_state = POPUP.open_config(
86 menu,
87 CONTEXT_MENU_ANCHOR_VAR.map_ref(move |(c, s)| if is_shortcut { s } else { c }),
88 CONTEXT_CAPTURE_VAR.get(),
89 );
90 }
91 }
92 }
93 }
94_ => {}
95 })
96}
9798/// Set the position of the context-menu widgets opened for the widget or its descendants.
99///
100/// This property defines two positions, `(click, shortcut)`, the first is used for context clicks
101/// from a pointer device, the second is used for context clicks from keyboard shortcut.
102///
103/// By default tips are aligned to cursor position at the time they are opened or the top for shortcut.
104///
105/// This property sets the [`CONTEXT_MENU_ANCHOR_VAR`].
106#[property(CONTEXT, default(CONTEXT_MENU_ANCHOR_VAR))]
107pub fn context_menu_anchor(child: impl UiNode, click_shortcut: impl IntoVar<(AnchorMode, AnchorMode)>) -> impl UiNode {
108 with_context_var(child, CONTEXT_MENU_ANCHOR_VAR, click_shortcut)
109}
110111/// Context menu popup.
112///
113/// This widget can be set in [`context_menu`] to define a popup menu that shows when the widget receives
114/// a context click.
115///
116/// [`context_menu`]: fn@context_menu
117#[widget($crate::context::ContextMenu {
118 ($children:expr) => {
119 children = $children;
120 }
121})]
122pub struct ContextMenu(crate::popup::SubMenuPopup);
123impl ContextMenu {
124fn widget_intrinsic(&mut self) {
125self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
126widget_set! {
127self;
128 alt_focus_scope = true;
129 style_base_fn = style_fn!(|_| DefaultStyle!());
130 }
131 }
132}
133impl_style_fn!(ContextMenu);
134135/// Arguments for context menu widget functions.
136pub struct ContextMenuArgs {
137/// ID of the widget the menu is anchored to.
138pub anchor_id: WidgetId,
139140/// Is `true` if the menu is for [`disabled_context_menu_fn`], is `false` for [`context_menu_fn`].
141 ///
142 /// [`context_menu_fn`]: fn@context_menu_fn
143 /// [`disabled_context_menu_fn`]: fn@disabled_context_menu_fn
144pub disabled: bool,
145}
146147context_var! {
148/// Position of the context widget in relation to the anchor widget.
149 ///
150 /// By default the context widget is shown at the cursor.
151pub static CONTEXT_MENU_ANCHOR_VAR: (AnchorMode, AnchorMode) = (AnchorMode::context_menu(), AnchorMode::context_menu_shortcut());
152153/// Defines the layout widget for [`ContextMenu!`].
154 ///
155 /// Is [`popup::default_panel_fn`] by default.
156 ///
157 /// [`ContextMenu!`]: struct@ContextMenu
158 /// [`popup::default_panel_fn`]: crate::popup::default_panel_fn
159pub static PANEL_FN_VAR: WidgetFn<zng_wgt_panel::PanelArgs> = WidgetFn::new(crate::popup::default_panel_fn);
160}
161162/// Widget function that generates the context menu layout.
163///
164/// This property sets [`PANEL_FN_VAR`].
165#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(ContextMenu))]
166pub fn panel_fn(child: impl UiNode, panel: impl IntoVar<WidgetFn<zng_wgt_panel::PanelArgs>>) -> impl UiNode {
167 with_context_var(child, PANEL_FN_VAR, panel)
168}
169170/// Context menu popup default style.
171#[widget($crate::context::DefaultStyle)]
172pub struct DefaultStyle(crate::popup::DefaultStyle);
173impl DefaultStyle {
174fn widget_intrinsic(&mut self) {
175widget_set! {
176self;
177 replace = true;
178 zng_wgt_button::style_fn = style_fn!(|_| super::ButtonStyle!());
179 zng_wgt_toggle::style_fn = style_fn!(|_| super::ToggleStyle!());
180 zng_wgt_rule_line::hr::color = BASE_COLOR_VAR.shade(1);
181 zng_wgt_text::icon::ico_size = 18;
182 }
183 }
184}
185186/// Touch context menu popup default style.
187#[widget($crate::context::TouchStyle)]
188pub struct TouchStyle(crate::popup::DefaultStyle);
189impl TouchStyle {
190fn widget_intrinsic(&mut self) {
191widget_set! {
192self;
193194 panel_fn = wgt_fn!(|args: zng_wgt_panel::PanelArgs| Stack! {
195 direction = StackDirection::left_to_right();
196 children = args.children;
197 });
198 zng_wgt_button::style_fn = style_fn!(|_| super::TouchButtonStyle!());
199 zng_wgt_rule_line::vr::color = BASE_COLOR_VAR.shade(1);
200 }
201 }
202}