1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Mouse and touch capture properties.

use zng_ext_input::{
    mouse::MOUSE_INPUT_EVENT,
    pointer_capture::{PointerCaptureArgs, POINTER_CAPTURE, POINTER_CAPTURE_EVENT},
    touch::TOUCH_INPUT_EVENT,
};
use zng_wgt::prelude::*;

pub use zng_ext_input::pointer_capture::CaptureMode;

event_property! {
    /// Widget acquired mouse and touch capture.
    pub fn got_pointer_capture {
        event: POINTER_CAPTURE_EVENT,
        args: PointerCaptureArgs,
        filter: |args| args.is_got(WIDGET.id()),
    }

    /// Widget lost mouse and touch capture.
    pub fn lost_pointer_capture {
        event: POINTER_CAPTURE_EVENT,
        args: PointerCaptureArgs,
        filter: |args| args.is_lost(WIDGET.id()),
    }

    /// Widget acquired or lost mouse and touch capture.
    pub fn pointer_capture_changed {
        event: POINTER_CAPTURE_EVENT,
        args: PointerCaptureArgs,
    }
}

/// Capture mouse and touch for the widget on press.
///
/// The capture happens only if any mouse button or touch is pressed on the window and the `mode` is [`Widget`] or [`Subtree`].
///
/// Captures are released when all mouse buttons and touch contacts stop being pressed on the window.
/// The capture is also released back to window if the `mode` changes to [`Window`].
///
/// [`Widget`]: CaptureMode::Widget
/// [`Subtree`]: CaptureMode::Subtree
/// [`Window`]: CaptureMode::Window
#[property(CONTEXT, default(false))]
pub fn capture_pointer(child: impl UiNode, mode: impl IntoVar<CaptureMode>) -> impl UiNode {
    let mode = mode.into_var();
    match_node(child, move |_, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_event(&MOUSE_INPUT_EVENT).sub_event(&TOUCH_INPUT_EVENT).sub_var(&mode);
        }
        UiNodeOp::Event { update } => {
            let mut capture = false;
            if let Some(args) = MOUSE_INPUT_EVENT.on(update) {
                capture = args.is_mouse_down();
            } else if let Some(args) = TOUCH_INPUT_EVENT.on(update) {
                capture = args.is_touch_start();
            }

            if capture {
                let widget_id = WIDGET.id();

                match mode.get() {
                    CaptureMode::Widget => {
                        POINTER_CAPTURE.capture_widget(widget_id);
                    }
                    CaptureMode::Subtree => {
                        POINTER_CAPTURE.capture_subtree(widget_id);
                    }
                    CaptureMode::Window => (),
                }
            }
        }
        UiNodeOp::Update { .. } => {
            if let Some(new_mode) = mode.get_new() {
                let tree = WINDOW.info();
                let widget_id = WIDGET.id();
                if tree.get(widget_id).map(|w| w.interactivity().is_enabled()).unwrap_or(false) {
                    if let Some(current) = POINTER_CAPTURE.current_capture().get() {
                        if current.target.widget_id() == widget_id {
                            // If mode updated and we are capturing the mouse:
                            match new_mode {
                                CaptureMode::Widget => POINTER_CAPTURE.capture_widget(widget_id),
                                CaptureMode::Subtree => POINTER_CAPTURE.capture_subtree(widget_id),
                                CaptureMode::Window => POINTER_CAPTURE.release_capture(),
                            }
                        }
                    }
                }
            }
        }
        _ => {}
    })
}

/// Capture mouse and touch for the widget on init.
///
/// The capture happens only if any mouse button or touch is pressed on the window and the `mode` is [`Widget`] or [`Subtree`].
///
/// Pointer captures are released when all mouse buttons stop being pressed on the window.
/// The capture is also released back to window if the `mode` changes to [`Window`] when the mouse is captured for the widget.
///
/// [`Widget`]: CaptureMode::Widget
/// [`Subtree`]: CaptureMode::Subtree
/// [`Window`]: CaptureMode::Window
#[property(CONTEXT, default(false))]
pub fn capture_pointer_on_init(child: impl UiNode, mode: impl IntoVar<CaptureMode>) -> impl UiNode {
    let mode = mode.into_var();
    let mut capture = true;

    match_node(child, move |_, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_var(&mode);
            capture = true; // wait for info
        }
        UiNodeOp::Info { .. } => {
            if std::mem::take(&mut capture) {
                let widget_id = WIDGET.id();

                match mode.get() {
                    CaptureMode::Widget => {
                        POINTER_CAPTURE.capture_widget(widget_id);
                    }
                    CaptureMode::Subtree => {
                        POINTER_CAPTURE.capture_subtree(widget_id);
                    }
                    CaptureMode::Window => (),
                }
            }
        }
        UiNodeOp::Update { .. } => {
            if let Some(new_mode) = mode.get_new() {
                let tree = WINDOW.info();
                let widget_id = WIDGET.id();
                if tree.get(widget_id).map(|w| w.interactivity().is_enabled()).unwrap_or(false) {
                    if let Some(current) = POINTER_CAPTURE.current_capture().get() {
                        if current.target.widget_id() == widget_id {
                            // If mode updated and we are capturing the mouse:
                            match new_mode {
                                CaptureMode::Widget => POINTER_CAPTURE.capture_widget(widget_id),
                                CaptureMode::Subtree => POINTER_CAPTURE.capture_subtree(widget_id),
                                CaptureMode::Window => POINTER_CAPTURE.release_capture(),
                            }
                        }
                    }
                }
            }
        }
        _ => {}
    })
}