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