1use colors::BASE_COLOR_VAR;
4use zng_ext_input::gesture::CLICK_EVENT;
5use zng_wgt::prelude::*;
6use zng_wgt_input::{focus::alt_focus_scope, gesture::mnemonic_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_named_style_fn, impl_style_fn, style_fn};
13
14#[property(EVENT)]
23pub fn context_menu(child: impl IntoUiNode, menu: impl IntoUiNode) -> UiNode {
24 context_menu_fn(child, WidgetFn::singleton(menu))
25}
26
27#[property(EVENT, default(WidgetFn::nil()))]
33pub fn context_menu_fn(child: impl IntoUiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>) -> UiNode {
34 context_menu_node(child, menu, false)
35}
36
37#[property(EVENT)]
43pub fn disabled_context_menu(child: impl IntoUiNode, menu: impl IntoUiNode) -> UiNode {
44 disabled_context_menu_fn(child, WidgetFn::singleton(menu))
45}
46
47#[property(EVENT, default(WidgetFn::nil()))]
53pub fn disabled_context_menu_fn(child: impl IntoUiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>) -> UiNode {
54 context_menu_node(child, menu, true)
55}
56
57fn context_menu_node(child: impl IntoUiNode, menu: impl IntoVar<WidgetFn<ContextMenuArgs>>, disabled_only: bool) -> UiNode {
58 let menu = menu.into_var();
59 let mut pop_state = var(PopupState::Closed).read_only();
60
61 match_node(child, move |c, op| match op {
62 UiNodeOp::Init => {
63 WIDGET.sub_var(&menu).sub_event_when(&CLICK_EVENT, |args| args.is_context());
64 }
65 UiNodeOp::Deinit => {
66 POPUP.close(&pop_state);
67 }
68 UiNodeOp::Update { updates } => {
69 c.update(updates);
70 CLICK_EVENT.each_update(false, |args| {
71 if args.is_context() {
72 let apply = if disabled_only {
73 args.target.interactivity().is_disabled()
74 } else {
75 args.target.interactivity().is_enabled()
76 };
77 if apply {
78 args.propagation.stop();
79
80 let menu = menu.get()(ContextMenuArgs {
81 anchor_id: WIDGET.id(),
82 disabled: disabled_only,
83 });
84 let is_shortcut = args.is_from_keyboard();
85 pop_state = POPUP.open_config(
86 menu,
87 CONTEXT_MENU_ANCHOR_VAR.map(move |(c, s)| if is_shortcut { s } else { c }.clone()),
88 CONTEXT_CAPTURE_VAR.get(),
89 );
90 }
91 }
92 });
93 }
94 _ => {}
95 })
96}
97
98#[property(CONTEXT, default(CONTEXT_MENU_ANCHOR_VAR))]
107pub fn context_menu_anchor(child: impl IntoUiNode, click_shortcut: impl IntoVar<(AnchorMode, AnchorMode)>) -> UiNode {
108 with_context_var(child, CONTEXT_MENU_ANCHOR_VAR, click_shortcut)
109}
110
111#[widget($crate::context::ContextMenu {
118 ($children:expr) => {
119 children = $children;
120 }
121})]
122pub struct ContextMenu(crate::popup::SubMenuPopup);
123impl ContextMenu {
124 fn widget_intrinsic(&mut self) {
125 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
126 widget_set! {
127 self;
128 alt_focus_scope = true;
129 mnemonic_scope = true;
130 zng_wgt_rule_line::collapse_scope = true;
131 }
132 }
133}
134impl_style_fn!(ContextMenu, DefaultStyle);
135
136#[non_exhaustive]
138pub struct ContextMenuArgs {
139 pub anchor_id: WidgetId,
141
142 pub disabled: bool,
147}
148impl ContextMenuArgs {
149 pub fn new(anchor_id: impl Into<WidgetId>, disabled: bool) -> Self {
151 Self {
152 anchor_id: anchor_id.into(),
153 disabled,
154 }
155 }
156}
157
158context_var! {
159 pub static CONTEXT_MENU_ANCHOR_VAR: (AnchorMode, AnchorMode) = (AnchorMode::context_menu(), AnchorMode::context_menu_shortcut());
163
164 pub static PANEL_FN_VAR: WidgetFn<zng_wgt_panel::PanelArgs> = WidgetFn::new(crate::popup::default_panel_fn);
171}
172
173#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(ContextMenu, DefaultStyle))]
177pub fn panel_fn(child: impl IntoUiNode, panel: impl IntoVar<WidgetFn<zng_wgt_panel::PanelArgs>>) -> UiNode {
178 with_context_var(child, PANEL_FN_VAR, panel)
179}
180
181#[widget($crate::context::DefaultStyle)]
183pub struct DefaultStyle(crate::popup::DefaultStyle);
184impl DefaultStyle {
185 fn widget_intrinsic(&mut self) {
186 widget_set! {
187 self;
188 replace = true;
189 zng_wgt_button::style_fn = style_fn!(|_| super::sub::ButtonStyle!());
190 zng_wgt_toggle::style_fn = style_fn!(|_| super::sub::ToggleStyle!());
191 zng_wgt_rule_line::hr::color = BASE_COLOR_VAR.shade(1);
192 zng_wgt_rule_line::vr::color = BASE_COLOR_VAR.shade(1);
193 zng_wgt_rule_line::vr::height = 1.em();
194 zng_wgt_text::icon::ico_size = 18;
195 }
196 }
197}
198
199#[widget($crate::context::TouchStyle)]
201pub struct TouchStyle(crate::popup::DefaultStyle);
202impl_named_style_fn!(touch, TouchStyle);
203impl TouchStyle {
204 fn widget_intrinsic(&mut self) {
205 widget_set! {
206 self;
207 named_style_fn = TOUCH_STYLE_FN_VAR;
208
209 panel_fn = wgt_fn!(|args: zng_wgt_panel::PanelArgs| Stack! {
210 direction = StackDirection::left_to_right();
211 children = args.children;
212 });
213 zng_wgt_button::style_fn = style_fn!(|_| super::sub::TouchButtonStyle!());
214 }
215 }
216}