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 self.target.window_id() == wgt.0
178 && let Some(wgt) = WINDOWS.widget_tree(wgt.0).and_then(|t| t.get(wgt.1))
179 {
180 for wgt in wgt.self_and_ancestors() {
181 if wgt.id() == self.target.widget_id() {
182 return true;
183 }
184 }
185 }
186 false
187 }
188 }
189 }
190}
191
192app_local! {
193 static POINTER_CAPTURE_SV: PointerCaptureService = {
194 hooks();
195 PointerCaptureService {
196 capture_value: None,
197 capture: var(None),
198
199 mouse_down: Default::default(),
200 touch_down: Default::default(),
201 }
202 };
203}
204
205struct PointerCaptureService {
206 capture_value: Option<CaptureInfo>,
207 capture: Var<Option<CaptureInfo>>,
208
209 mouse_down: HashSet<(WindowId, InputDeviceId, MouseButton)>,
210 touch_down: HashSet<(WindowId, InputDeviceId, TouchId)>,
211}
212
213event! {
214 pub static POINTER_CAPTURE_EVENT: PointerCaptureArgs {
216 let _ = POINTER_CAPTURE_SV.read();
217 };
218}
219
220event_args! {
221 pub struct PointerCaptureArgs {
223 pub prev_capture: Option<CaptureInfo>,
225 pub new_capture: Option<CaptureInfo>,
227
228 ..
229
230 fn is_in_target(&self, id: WidgetId) -> bool {
235 if let Some(p) = &self.prev_capture
236 && p.target.contains(id)
237 {
238 return true;
239 }
240 if let Some(p) = &self.new_capture
241 && p.target.contains(id)
242 {
243 return true;
244 }
245 false
246 }
247 }
248}
249
250impl PointerCaptureArgs {
251 pub fn is_widget_move(&self) -> bool {
253 match (&self.prev_capture, &self.new_capture) {
254 (Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.target != new.target,
255 _ => false,
256 }
257 }
258
259 pub fn is_mode_change(&self) -> bool {
261 match (&self.prev_capture, &self.new_capture) {
262 (Some(prev), Some(new)) => prev.target.widget_id() == new.target.widget_id() && prev.mode != new.mode,
263 _ => false,
264 }
265 }
266
267 pub fn is_lost(&self, widget_id: WidgetId) -> bool {
269 match (&self.prev_capture, &self.new_capture) {
270 (None, _) => false,
271 (Some(p), None) => p.target.widget_id() == widget_id,
272 (Some(prev), Some(new)) => prev.target.widget_id() == widget_id && new.target.widget_id() != widget_id,
273 }
274 }
275
276 pub fn is_got(&self, widget_id: WidgetId) -> bool {
278 match (&self.prev_capture, &self.new_capture) {
279 (_, None) => false,
280 (None, Some(p)) => p.target.widget_id() == widget_id,
281 (Some(prev), Some(new)) => prev.target.widget_id() != widget_id && new.target.widget_id() == widget_id,
282 }
283 }
284}
285
286fn hooks() {
287 WIDGET_TREE_CHANGED_EVENT
288 .hook(|args| {
289 let mut s = POINTER_CAPTURE_SV.write();
290 if let Some(c) = &s.capture_value
291 && c.target.window_id() == args.tree.window_id()
292 {
293 s.continue_capture(&args.tree);
294 }
295 true
296 })
297 .perm();
298
299 RAW_MOUSE_INPUT_EVENT
300 .hook(|args| {
301 let mut s = POINTER_CAPTURE_SV.write();
302 match args.state {
303 ButtonState::Pressed => {
304 if s.mouse_down.insert((args.window_id, args.device_id, args.button))
305 && s.mouse_down.len() == 1
306 && s.touch_down.is_empty()
307 {
308 s.on_first_down(args.window_id);
309 }
310 }
311 ButtonState::Released => {
312 if s.mouse_down.remove(&(args.window_id, args.device_id, args.button))
313 && s.mouse_down.is_empty()
314 && s.touch_down.is_empty()
315 {
316 s.on_last_up();
317 }
318 }
319 }
320 true
321 })
322 .perm();
323
324 RAW_TOUCH_EVENT
325 .hook(|args| {
326 let mut s = POINTER_CAPTURE_SV.write();
327 for touch in &args.touches {
328 match touch.phase {
329 TouchPhase::Start => {
330 if s.touch_down.insert((args.window_id, args.device_id, touch.touch))
331 && s.touch_down.len() == 1
332 && s.mouse_down.is_empty()
333 {
334 s.on_first_down(args.window_id);
335 }
336 }
337 TouchPhase::End | TouchPhase::Cancel => {
338 if s.touch_down.remove(&(args.window_id, args.device_id, touch.touch))
339 && s.touch_down.is_empty()
340 && s.mouse_down.is_empty()
341 {
342 s.on_last_up();
343 }
344 }
345 TouchPhase::Move => {}
346 }
347 }
348 true
349 })
350 .perm();
351
352 RAW_WINDOW_CLOSE_EVENT
353 .hook(|args| {
354 POINTER_CAPTURE_SV.write().remove_window(args.window_id);
355 true
356 })
357 .perm();
358
359 fn nest_parent(id: WindowId) -> Option<WindowId> {
360 WINDOWS
361 .vars(id)
362 .and_then(|v| if v.nest_parent().get().is_some() { v.parent().get() } else { None })
363 }
364
365 RAW_WINDOW_FOCUS_EVENT
366 .hook(|args| {
367 let actual_prev = args.prev_focus.map(|id| nest_parent(id).unwrap_or(id));
368 let actual_new = args.new_focus.map(|id| nest_parent(id).unwrap_or(id));
369
370 if actual_prev == actual_new {
371 return true;
373 }
374
375 if let Some(w) = actual_prev {
376 POINTER_CAPTURE_SV.write().remove_window(w);
377 }
378 true
379 })
380 .perm();
381
382 VIEW_PROCESS_INITED_EVENT
383 .hook(|args| {
384 if args.is_respawn {
385 let mut s = POINTER_CAPTURE_SV.write();
386
387 if !s.mouse_down.is_empty() || !s.touch_down.is_empty() {
388 s.mouse_down.clear();
389 s.touch_down.clear();
390 s.on_last_up();
391 }
392 }
393 true
394 })
395 .perm();
396}
397impl PointerCaptureService {
398 fn remove_window(&mut self, window_id: WindowId) {
399 if !self.mouse_down.is_empty() || !self.touch_down.is_empty() {
400 self.mouse_down.retain(|(w, _, _)| *w != window_id);
401 self.touch_down.retain(|(w, _, _)| *w != window_id);
402
403 if self.mouse_down.is_empty() && self.touch_down.is_empty() {
404 self.on_last_up();
405 }
406 }
407 }
408
409 fn on_first_down(&mut self, window_id: WindowId) {
410 if let Some(info) = WINDOWS.widget_tree(window_id) {
411 self.set_capture(info.root().interaction_path(), CaptureMode::Window);
413 }
414 }
415
416 fn on_last_up(&mut self) {
417 self.unset_capture();
418 }
419
420 fn continue_capture(&mut self, info: &WidgetInfoTree) {
421 let current = self.capture_value.as_ref().unwrap();
422
423 if let Some(widget) = info.get(current.target.widget_id()) {
424 if let Some(new_path) = widget.new_interaction_path(&InteractionPath::from_enabled(current.target.clone())) {
425 let mode = current.mode;
427 self.set_capture(new_path, mode);
428 }
429 } else {
430 self.set_capture(info.root().interaction_path(), CaptureMode::Window);
432 }
433 }
434
435 fn set_capture(&mut self, target: InteractionPath, mode: CaptureMode) {
436 let new = target.enabled().map(|target| CaptureInfo { target, mode });
437 if new.is_none() {
438 self.unset_capture();
439 return;
440 }
441 if new != self.capture_value {
442 let prev = self.capture_value.take();
443 self.capture_value.clone_from(&new);
444 self.capture.set(new.clone());
445 POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, new));
446 }
447 }
448
449 fn unset_capture(&mut self) {
450 if self.capture_value.is_some() {
451 let prev = self.capture_value.take();
452 self.capture_value = None;
453 self.capture.set(None);
454 POINTER_CAPTURE_EVENT.notify(PointerCaptureArgs::now(prev, None));
455 }
456 }
457}