1use 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_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(&CLICK_EVENT);
64 }
65 UiNodeOp::Deinit => {
66 POPUP.close(&pop_state);
67 }
68 UiNodeOp::Event { update } => {
69 c.event(update);
70 if let Some(args) = CLICK_EVENT.on_unhandled(update)
71 && args.is_context()
72 {
73 let apply = if disabled_only {
74 args.target.interactivity().is_disabled()
75 } else {
76 args.target.interactivity().is_enabled()
77 };
78 if apply {
79 args.propagation().stop();
80
81 let menu = menu.get()(ContextMenuArgs {
82 anchor_id: WIDGET.id(),
83 disabled: disabled_only,
84 });
85 let is_shortcut = args.is_from_keyboard();
86 pop_state = POPUP.open_config(
87 menu,
88 CONTEXT_MENU_ANCHOR_VAR.map(move |(c, s)| if is_shortcut { s } else { c }.clone()),
89 CONTEXT_CAPTURE_VAR.get(),
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 zng_wgt_rule_line::collapse_scope = true;
130 }
131 }
132}
133impl_style_fn!(ContextMenu, DefaultStyle);
134
135#[non_exhaustive]
137pub struct ContextMenuArgs {
138 pub anchor_id: WidgetId,
140
141 pub disabled: bool,
146}
147impl ContextMenuArgs {
148 pub fn new(anchor_id: impl Into<WidgetId>, disabled: bool) -> Self {
150 Self {
151 anchor_id: anchor_id.into(),
152 disabled,
153 }
154 }
155}
156
157context_var! {
158 pub static CONTEXT_MENU_ANCHOR_VAR: (AnchorMode, AnchorMode) = (AnchorMode::context_menu(), AnchorMode::context_menu_shortcut());
162
163 pub static PANEL_FN_VAR: WidgetFn<zng_wgt_panel::PanelArgs> = WidgetFn::new(crate::popup::default_panel_fn);
170}
171
172#[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(ContextMenu, DefaultStyle))]
176pub fn panel_fn(child: impl IntoUiNode, panel: impl IntoVar<WidgetFn<zng_wgt_panel::PanelArgs>>) -> UiNode {
177 with_context_var(child, PANEL_FN_VAR, panel)
178}
179
180#[widget($crate::context::DefaultStyle)]
182pub struct DefaultStyle(crate::popup::DefaultStyle);
183impl DefaultStyle {
184 fn widget_intrinsic(&mut self) {
185 widget_set! {
186 self;
187 replace = true;
188 zng_wgt_button::style_fn = style_fn!(|_| super::sub::ButtonStyle!());
189 zng_wgt_toggle::style_fn = style_fn!(|_| super::sub::ToggleStyle!());
190 zng_wgt_rule_line::hr::color = BASE_COLOR_VAR.shade(1);
191 zng_wgt_rule_line::vr::color = BASE_COLOR_VAR.shade(1);
192 zng_wgt_rule_line::vr::height = 1.em();
193 zng_wgt_text::icon::ico_size = 18;
194 }
195 }
196}
197
198#[widget($crate::context::TouchStyle)]
200pub struct TouchStyle(crate::popup::DefaultStyle);
201impl_named_style_fn!(touch, TouchStyle);
202impl TouchStyle {
203 fn widget_intrinsic(&mut self) {
204 widget_set! {
205 self;
206 named_style_fn = TOUCH_STYLE_FN_VAR;
207
208 panel_fn = wgt_fn!(|args: zng_wgt_panel::PanelArgs| Stack! {
209 direction = StackDirection::left_to_right();
210 children = args.children;
211 });
212 zng_wgt_button::style_fn = style_fn!(|_| super::sub::TouchButtonStyle!());
213 }
214 }
215}