1use std::{collections::HashSet, time::Duration};
2
3use zng_app::timer::TIMERS;
4use zng_ext_input::{
5 gesture::{CLICK_EVENT, GESTURES},
6 mouse::{ClickMode, MOUSE_HOVERED_EVENT, MOUSE_INPUT_EVENT, WidgetInfoMouseExt as _},
7 pointer_capture::POINTER_CAPTURE_EVENT,
8 touch::TOUCHED_EVENT,
9};
10use zng_view_api::{mouse::ButtonState, touch::TouchPhase};
11use zng_wgt::prelude::*;
12
13#[property(EVENT)]
15pub fn is_hovered_disabled(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
16 event_state(child, state, false, MOUSE_HOVERED_EVENT, |args| {
17 if args.is_mouse_enter_disabled() {
18 Some(true)
19 } else if args.is_mouse_leave_disabled() {
20 Some(false)
21 } else {
22 None
23 }
24 })
25}
26
27#[property(EVENT)]
38pub fn is_hovered(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
39 event_state(child, state, false, MOUSE_HOVERED_EVENT, |args| {
40 if args.is_mouse_enter_enabled() {
41 Some(true)
42 } else if args.is_mouse_leave_enabled() {
43 Some(false)
44 } else {
45 None
46 }
47 })
48}
49
50#[property(EVENT)]
56pub fn is_cap_hovered(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
57 event_state2(
58 child,
59 state,
60 false,
61 MOUSE_HOVERED_EVENT,
62 false,
63 |hovered_args| {
64 if hovered_args.is_mouse_enter_enabled() {
65 Some(true)
66 } else if hovered_args.is_mouse_leave_enabled() {
67 Some(false)
68 } else {
69 None
70 }
71 },
72 POINTER_CAPTURE_EVENT,
73 false,
74 |cap_args| {
75 if cap_args.is_got(WIDGET.id()) {
76 Some(true)
77 } else if cap_args.is_lost(WIDGET.id()) {
78 Some(false)
79 } else {
80 None
81 }
82 },
83 |hovered, captured| Some(hovered || captured),
84 )
85}
86
87#[property(EVENT)]
101pub fn is_mouse_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
102 event_state3(
103 child,
104 state,
105 false,
106 MOUSE_HOVERED_EVENT,
107 false,
108 |hovered_args| {
109 if hovered_args.is_mouse_enter_enabled() {
110 Some(true)
111 } else if hovered_args.is_mouse_leave_enabled() {
112 Some(false)
113 } else {
114 None
115 }
116 },
117 MOUSE_INPUT_EVENT,
118 false,
119 |input_args| {
120 if input_args.is_primary() {
121 match input_args.state {
122 ButtonState::Pressed => {
123 if input_args.capture_allows() {
124 return Some(input_args.is_enabled(WIDGET.id()));
125 }
126 }
127 ButtonState::Released => return Some(false),
128 }
129 }
130 None
131 },
132 POINTER_CAPTURE_EVENT,
133 false,
134 |cap_args| {
135 if cap_args.is_got(WIDGET.id()) {
136 Some(true)
137 } else if cap_args.is_lost(WIDGET.id()) {
138 Some(false)
139 } else {
140 None
141 }
142 },
143 {
144 let mut info_gen = 0;
145 let mut mode = ClickMode::default();
146
147 move |hovered, is_down, is_captured| {
148 let tree = WINDOW.info();
150 if info_gen != tree.stats().generation {
151 mode = tree.get(WIDGET.id()).unwrap().click_mode();
152 info_gen = tree.stats().generation;
153 }
154
155 if mode.repeat {
156 Some(is_down || is_captured)
157 } else {
158 Some(hovered && is_down)
159 }
160 }
161 },
162 )
163}
164
165#[property(EVENT)]
169pub fn is_cap_mouse_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
170 event_state2(
171 child,
172 state,
173 false,
174 MOUSE_INPUT_EVENT,
175 false,
176 |input_args| {
177 if input_args.is_primary() {
178 match input_args.state {
179 ButtonState::Pressed => {
180 if input_args.capture_allows() {
181 return Some(input_args.is_enabled(WIDGET.id()));
182 }
183 }
184 ButtonState::Released => return Some(false),
185 }
186 }
187 None
188 },
189 POINTER_CAPTURE_EVENT,
190 false,
191 |cap_args| {
192 if cap_args.is_got(WIDGET.id()) {
193 Some(true)
194 } else if cap_args.is_lost(WIDGET.id()) {
195 Some(false)
196 } else {
197 None
198 }
199 },
200 |is_down, is_captured| Some(is_down || is_captured),
201 )
202}
203
204#[property(EVENT)]
208pub fn is_shortcut_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
209 let state = state.into_var();
210 let mut shortcut_press = None;
211
212 match_node(child, move |child, op| match op {
213 UiNodeOp::Init => {
214 let _ = state.set(false);
215 WIDGET.sub_event(&CLICK_EVENT);
216 }
217 UiNodeOp::Deinit => {
218 let _ = state.set(false);
219 }
220 UiNodeOp::Event { update } => {
221 if let Some(args) = CLICK_EVENT.on(update) {
222 if (args.is_from_keyboard() || args.is_from_access()) && args.is_enabled(WIDGET.id()) {
223 if shortcut_press.take().is_none() {
227 let duration = GESTURES.shortcut_pressed_duration().get();
228 if duration != Duration::default() {
229 let dl = TIMERS.deadline(duration);
230 dl.subscribe(UpdateOp::Update, WIDGET.id()).perm();
231 shortcut_press = Some(dl);
232 let _ = state.set(true);
233 }
234 } else {
235 let _ = state.set(false);
236 }
237 }
238 }
239 }
240 UiNodeOp::Update { updates } => {
241 child.update(updates);
242
243 if let Some(timer) = &shortcut_press {
244 if timer.is_new() {
245 shortcut_press = None;
246 let _ = state.set(false);
247 }
248 }
249 }
250 _ => {}
251 })
252}
253
254#[property(EVENT)]
268pub fn is_touched(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
269 event_state(child, state, false, TOUCHED_EVENT, |args| {
270 if args.is_touch_enter_enabled() {
271 Some(true)
272 } else if args.is_touch_leave_enabled() {
273 Some(false)
274 } else {
275 None
276 }
277 })
278}
279
280#[property(EVENT)]
290pub fn is_touched_from_start(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
291 #[expect(clippy::mutable_key_type)] let mut touches_started = HashSet::new();
293 event_state(child, state, false, TOUCHED_EVENT, move |args| {
294 if args.is_touch_enter_enabled() {
295 match args.phase {
296 TouchPhase::Start => {
297 touches_started.retain(|t: &EventPropagationHandle| !t.is_stopped()); touches_started.insert(args.touch_propagation.clone());
299 Some(true)
300 }
301 TouchPhase::Move => Some(touches_started.contains(&args.touch_propagation)),
302 TouchPhase::End | TouchPhase::Cancel => Some(false), }
304 } else if args.is_touch_leave_enabled() {
305 if let TouchPhase::End | TouchPhase::Cancel = args.phase {
306 touches_started.remove(&args.touch_propagation);
307 }
308 Some(false)
309 } else {
310 None
311 }
312 })
313}
314
315#[property(EVENT)]
321pub fn is_cap_touched(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
322 event_state2(
323 child,
324 state,
325 false,
326 TOUCHED_EVENT,
327 false,
328 |hovered_args| {
329 if hovered_args.is_touch_enter_enabled() {
330 Some(true)
331 } else if hovered_args.is_touch_leave_enabled() {
332 Some(false)
333 } else {
334 None
335 }
336 },
337 POINTER_CAPTURE_EVENT,
338 false,
339 |cap_args| {
340 if cap_args.is_got(WIDGET.id()) {
341 Some(true)
342 } else if cap_args.is_lost(WIDGET.id()) {
343 Some(false)
344 } else {
345 None
346 }
347 },
348 |hovered, captured| Some(hovered || captured),
349 )
350}
351
352#[property(EVENT)]
358pub fn is_cap_touched_from_start(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
359 #[expect(clippy::mutable_key_type)] let mut touches_started = HashSet::new();
361 event_state2(
362 child,
363 state,
364 false,
365 TOUCHED_EVENT,
366 false,
367 move |hovered_args| {
368 if hovered_args.is_touch_enter_enabled() {
369 match hovered_args.phase {
370 TouchPhase::Start => {
371 touches_started.retain(|t: &EventPropagationHandle| !t.is_stopped()); touches_started.insert(hovered_args.touch_propagation.clone());
373 Some(true)
374 }
375 TouchPhase::Move => Some(touches_started.contains(&hovered_args.touch_propagation)),
376 TouchPhase::End | TouchPhase::Cancel => Some(false), }
378 } else if hovered_args.is_touch_leave_enabled() {
379 if let TouchPhase::End | TouchPhase::Cancel = hovered_args.phase {
380 touches_started.remove(&hovered_args.touch_propagation);
381 }
382 Some(false)
383 } else {
384 None
385 }
386 },
387 POINTER_CAPTURE_EVENT,
388 false,
389 |cap_args| {
390 if cap_args.is_got(WIDGET.id()) {
391 Some(true)
392 } else if cap_args.is_lost(WIDGET.id()) {
393 Some(false)
394 } else {
395 None
396 }
397 },
398 |hovered, captured| Some(hovered || captured),
399 )
400}
401
402#[property(EVENT)]
411pub fn is_pointer_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
412 let pressed = state_var();
413 let child = is_mouse_pressed(child, pressed.clone());
414
415 let touched = state_var();
416 let child = is_touched_from_start(child, touched.clone());
417
418 bind_state(child, merge_var!(pressed, touched, |&p, &t| p || t), state)
419}
420
421#[property(EVENT)]
431pub fn is_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
432 let pressed = state_var();
433 let child = is_mouse_pressed(child, pressed.clone());
434
435 let touched = state_var();
436 let child = is_touched_from_start(child, touched.clone());
437
438 let shortcut_pressed = state_var();
439 let child = is_shortcut_pressed(child, shortcut_pressed.clone());
440
441 bind_state(
442 child,
443 merge_var!(pressed, touched, shortcut_pressed, |&p, &t, &s| p || t || s),
444 state,
445 )
446}
447
448#[property(EVENT)]
453pub fn is_cap_pointer_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
454 let pressed = state_var();
455 let child = is_cap_mouse_pressed(child, pressed.clone());
456
457 let touched = state_var();
458 let child = is_cap_touched_from_start(child, touched.clone());
459
460 bind_state(child, merge_var!(pressed, touched, |&p, &t| p || t), state)
461}
462
463#[property(EVENT)]
469pub fn is_cap_pressed(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
470 let pressed = state_var();
471 let child = is_cap_mouse_pressed(child, pressed.clone());
472
473 let touched = state_var();
474 let child = is_cap_touched_from_start(child, touched.clone());
475
476 let shortcut_pressed = state_var();
477 let child = is_shortcut_pressed(child, pressed.clone());
478
479 bind_state(
480 child,
481 merge_var!(pressed, touched, shortcut_pressed, |&p, &t, &s| p || t || s),
482 state,
483 )
484}