1use std::{
16 collections::{HashMap, HashSet},
17 fmt,
18};
19
20use zng_app::{
21 event::{event, event_args},
22 update::UPDATES,
23 view_process::{
24 VIEW_PROCESS_INITED_EVENT,
25 raw_device_events::InputDeviceId,
26 raw_events::{RAW_MOUSE_INPUT_EVENT, RAW_MOUSE_MOVED_EVENT, RAW_TOUCH_EVENT, RAW_WINDOW_CLOSE_EVENT, RAW_WINDOW_FOCUS_EVENT},
27 },
28 widget::{
29 WidgetId,
30 info::{InteractionPath, WIDGET_TREE_CHANGED_EVENT, WidgetInfoTree, WidgetPath},
31 },
32 window::WindowId,
33};
34use zng_app_context::app_local;
35use zng_ext_window::{NestedWindowWidgetInfoExt, WINDOWS};
36use zng_layout::unit::{DipPoint, DipToPx};
37use zng_var::{Var, impl_from_and_into_var, var};
38use zng_view_api::{
39 mouse::{ButtonState, MouseButton},
40 touch::{TouchId, TouchPhase},
41};
42
43#[expect(non_camel_case_types)]
55pub struct POINTER_CAPTURE;
56impl POINTER_CAPTURE {
57 pub fn current_capture(&self) -> Var<Option<CaptureInfo>> {
59 POINTER_CAPTURE_SV.read().capture.read_only()
60 }
61
62 pub fn capture_widget(&self, widget_id: WidgetId) {
66 self.capture_impl(widget_id, CaptureMode::Widget);
67 }
68
69 pub fn capture_subtree(&self, widget_id: WidgetId) {
76 self.capture_impl(widget_id, CaptureMode::Subtree);
77 }
78
79 fn capture_impl(&self, widget_id: WidgetId, mode: CaptureMode) {
80 UPDATES.once_update("POINTER_CAPTURE.capture", move || {
81 let mut s = POINTER_CAPTURE_SV.write();
82 if let Some(cap) = &s.capture_value {
83 if let Some(wgt) = WINDOWS.widget_tree(cap.target.window_id()).and_then(|t| t.get(widget_id)) {
84 s.set_capture(wgt.interaction_path(), mode);
85 } else {
86 tracing::debug!("ignoring capture request for {widget_id}, no found in pressed window");
87 }
88 } else {
89 tracing::debug!("ignoring capture request for {widget_id}, no window is pressed");
90 }
91 });
92 }
93
94 pub fn release_capture(&self) {
99 UPDATES.once_update("POINTER_CAPTURE.release_capture", move || {
100 let mut s = POINTER_CAPTURE_SV.write();
101 if let Some(cap) = &s.capture_value
102 && cap.mode != CaptureMode::Window
103 {
104 let target = cap.target.root_path().into_owned();
106 s.set_capture(InteractionPath::from_enabled(target), CaptureMode::Window);
107 } else {
108 tracing::debug!("ignoring release_capture request, no widget or subtree holding capture");
109 }
110 });
111 }
112}
113
114#[derive(Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
116pub enum CaptureMode {
117 Window,
121 Subtree,
124
125 Widget,
127}
128impl fmt::Debug for CaptureMode {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 if f.alternate() {
131 write!(f, "CaptureMode::")?;
132 }
133 match self {
134 CaptureMode::Window => write!(f, "Window"),
135 CaptureMode::Subtree => write!(f, "Subtree"),
136 CaptureMode::Widget => write!(f, "Widget"),
137 }
138 }
139}
140impl Default for CaptureMode {
141 fn default() -> Self {
143 CaptureMode::Window
144 }
145}
146impl_from_and_into_var! {
147 fn from(widget: bool) -> CaptureMode {
149 if widget { CaptureMode::Widget } else { CaptureMode::Window }
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct CaptureInfo {
156 pub target: WidgetPath,
162 pub mode: CaptureMode,
164}
165impl CaptureInfo {
166 pub fn allows(&self, wgt: (WindowId, WidgetId)) -> bool {
177 match self.mode {
178 CaptureMode::Window => self.target.window_id() == wgt.0,
179 CaptureMode::Widget => self.target.widget_id() == wgt.1,
180 CaptureMode::Subtree => {
181 if let Some(wgt) = WINDOWS.widget_tree(wgt.0).and_then(|t| t.get(wgt.1)) {
182 for wgt in wgt.self_and_ancestors() {
183 if wgt.id() == self.target.widget_id() {
184 return true;
185 }
186 }
187 }
188 false
189 }
190 }
191 }
192}
193
194app_local! {
195 static POINTER_CAPTURE_SV: PointerCaptureService = {
196 hooks();
197 PointerCaptureService {
198 capture_value: None,
199 capture: var(None),
200
201 mouse_position: Default::default(),
202 mouse_down: Default::default(),
203 touch_down: Default::default(),
204 }
205 };
206}
207
208struct PointerCaptureService {
209 capture_value: Option<CaptureInfo>,
210 capture: Var<Option<CaptureInfo>>,
211
212 mouse_position: HashMap<(WindowId, InputDeviceId), DipPoint>,
213 mouse_down: HashSet<(WindowId, InputDeviceId, MouseButton)>,
214 touch_down: HashSet<(WindowId, InputDeviceId, TouchId)>,
215}
216
217event! {
218 pub static POINTER_CAPTURE_EVENT: PointerCaptureArgs {
220 let _ = POINTER_CAPTURE_SV.read();
221 };
222}
223
224event_args! {
225 pub struct PointerCaptureArgs {
227 pub prev_capture: Option<CaptureInfo>,
229 pub new_capture: Option<CaptureInfo>,
231
232 ..
233
234 fn is_in_target(&self, id: WidgetId) -> bool {
239 if let Some(p) = &self.prev_capture
240 && p.target.contains(id)
241 {
242 return true;
243 }
244 if let Some(p) = &self.new_capture
245 && p.target.contains(id)
246 {
247 return true;
248 }
249 false
250 }
251 }
252}
253
254impl PointerCaptureArgs {
255 pub fn is_widget_move(&self) -> bool {
257 match (&self.prev_capture, &self.new_capture) {
258 (Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.target != new.target,
259 _ => false,
260 }
261 }
262
263 pub fn is_mode_change(&self) -> bool {
265 match (&self.prev_capture, &self.new_capture) {
266 (Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.mode != new.mode,
267 _ => false,
268 }
269 }
270
271 pub fn is_lost(&self, widget_id: WidgetId) -> bool {
273 match (&self.prev_capture, &self.new_capture) {
274 (None, _) => false,
275 (Some(p), None) => p.target.widget_id() == widget_id,
276 (Some(prev), Some(new)) => prev.target.widget_id() == widget_id && new.target.widget_id() != widget_id,
277 }
278 }
279
280 pub fn is_got(&self, widget_id: WidgetId) -> bool {
282 match (&self.prev_capture, &self.new_capture) {
283 (_, None) => false,
284 (None, Some(p)) => p.target.widget_id() == widget_id,
285 (Some(prev), Some(new)) => prev.target.widget_id() != widget_id && new.target.widget_id() == widget_id,
286 }
287 }
288}
289
290fn hooks() {
291 WIDGET_TREE_CHANGED_EVENT
292 .hook(|args| {
293 let mut s = POINTER_CAPTURE_SV.write();
294 if let Some(c) = &s.capture_value
295 && c.target.window_id() == args.tree.window_id()
296 {
297 s.continue_capture(&args.tree);
298 }
299 true
300 })
301 .perm();
302
303 RAW_MOUSE_MOVED_EVENT
304 .hook(|args| {
305 POINTER_CAPTURE_SV
306 .write()
307 .mouse_position
308 .insert((args.window_id, args.device_id), args.position);
309 true
310 })
311 .perm();
312
313 RAW_MOUSE_INPUT_EVENT
314 .hook(|args| {
315 let mut s = POINTER_CAPTURE_SV.write();
316 match args.state {
317 ButtonState::Pressed => {
318 if s.mouse_down.insert((args.window_id, args.device_id, args.button))
319 && s.mouse_down.len() == 1
320 && s.touch_down.is_empty()
321 {
322 let point = s.mouse_position.get(&(args.window_id, args.device_id)).copied().unwrap_or_default();
323 s.on_first_down(args.window_id, point);
324 }
325 }
326 ButtonState::Released => {
327 if s.mouse_down.remove(&(args.window_id, args.device_id, args.button))
328 && s.mouse_down.is_empty()
329 && s.touch_down.is_empty()
330 {
331 s.on_last_up();
332 }
333 }
334 }
335 true
336 })
337 .perm();
338
339 RAW_TOUCH_EVENT
340 .hook(|args| {
341 let mut s = POINTER_CAPTURE_SV.write();
342 for touch in &args.touches {
343 match touch.phase {
344 TouchPhase::Start => {
345 if s.touch_down.insert((args.window_id, args.device_id, touch.touch))
346 && s.touch_down.len() == 1
347 && s.mouse_down.is_empty()
348 {
349 s.on_first_down(args.window_id, touch.position);
350 }
351 }
352 TouchPhase::End | TouchPhase::Cancel => {
353 if s.touch_down.remove(&(args.window_id, args.device_id, touch.touch))
354 && s.touch_down.is_empty()
355 && s.mouse_down.is_empty()
356 {
357 s.on_last_up();
358 }
359 }
360 TouchPhase::Move => {}
361 }
362 }
363 true
364 })
365 .perm();
366
367 RAW_WINDOW_CLOSE_EVENT
368 .hook(|args| {
369 POINTER_CAPTURE_SV.write().remove_window(args.window_id);
370 true
371 })
372 .perm();
373
374 fn nest_parent(id: WindowId) -> Option<WindowId> {
375 WINDOWS
376 .vars(id)
377 .and_then(|v| if v.nest_parent().get().is_some() { v.parent().get() } else { None })
378 }
379
380 RAW_WINDOW_FOCUS_EVENT
381 .hook(|args| {
382 let actual_prev = args.prev_focus.map(|id| nest_parent(id).unwrap_or(id));
383 let actual_new = args.new_focus.map(|id| nest_parent(id).unwrap_or(id));
384
385 if actual_prev == actual_new {
386 return true;
388 }
389
390 if let Some(w) = actual_prev {
391 POINTER_CAPTURE_SV.write().remove_window(w);
392 }
393 true
394 })
395 .perm();
396
397 VIEW_PROCESS_INITED_EVENT
398 .hook(|args| {
399 if args.is_respawn {
400 let mut s = POINTER_CAPTURE_SV.write();
401
402 if !s.mouse_down.is_empty() || !s.touch_down.is_empty() {
403 s.mouse_down.clear();
404 s.touch_down.clear();
405 s.on_last_up();
406 }
407 }
408 true
409 })
410 .perm();
411}
412impl PointerCaptureService {
413 fn remove_window(&mut self, window_id: WindowId) {
414 self.mouse_position.retain(|(w, _), _| *w != window_id);
415
416 if !self.mouse_down.is_empty() || !self.touch_down.is_empty() {
417 self.mouse_down.retain(|(w, _, _)| *w != window_id);
418 self.touch_down.retain(|(w, _, _)| *w != window_id);
419
420 if self.mouse_down.is_empty() && self.touch_down.is_empty() {
421 self.on_last_up();
422 }
423 }
424 }
425
426 fn on_first_down(&mut self, window_id: WindowId, point: DipPoint) {
427 if let Some(mut info) = WINDOWS.widget_tree(window_id) {
428 let mut point = point.to_px(info.scale_factor());
429
430 if let Some(t) = info.root().hit_test(point).target()
432 && let Some(w) = info.get(t.widget_id)
433 && let Some(t) = w.nested_window_tree()
434 {
435 info = t;
436 point = w
437 .inner_transform()
438 .inverse()
439 .and_then(|t| t.transform_point(point))
440 .unwrap_or(point);
441 }
442
443 self.set_capture(info.root().interaction_path(), CaptureMode::Window);
445 }
446 }
447
448 fn on_last_up(&mut self) {
449 self.unset_capture();
450 }
451
452 fn continue_capture(&mut self, info: &WidgetInfoTree) {
453 let current = self.capture_value.as_ref().unwrap();
454
455 if let Some(widget) = info.get(current.target.widget_id()) {
456 if let Some(new_path) = widget.new_interaction_path(&InteractionPath::from_enabled(current.target.clone())) {
457 let mode = current.mode;
459 self.set_capture(new_path, mode);
460 }
461 } else {
462 self.set_capture(info.root().interaction_path(), CaptureMode::Window);
464 }
465 }
466
467 fn set_capture(&mut self, target: InteractionPath, mode: CaptureMode) {
468 let new = target.enabled().map(|target| CaptureInfo { target, mode });
469 if new.is_none() {
470 self.unset_capture();
471 return;
472 }
473 if new != self.capture_value {
474 let prev = self.capture_value.take();
475 self.capture_value.clone_from(&new);
476 self.capture.set(new.clone());
477 POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, new));
478 }
479 }
480
481 fn unset_capture(&mut self) {
482 if self.capture_value.is_some() {
483 let prev = self.capture_value.take();
484 self.capture_value = None;
485 self.capture.set(None);
486 POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, None));
487 }
488 }
489}