Skip to main content

agg_gui/widget/app/
pointer.rs

1//! Pointer-input methods on [`App`] — mouse move/down/up plus the
2//! modal-subtree hit extension they share. Split out of `app.rs`
3//! (800-line guardrail); keyboard routing is in `keyboard.rs`, wheel
4//! routing stays in `app.rs`.
5
6use super::tree_paths::widget_at_path;
7use crate::event::{Event, Modifiers, MouseButton};
8use crate::geometry::Point;
9use crate::widget::tree::{active_modal_path, dispatch_event, hit_test_subtree};
10use crate::widget::tree_inspector::set_current_mouse_world;
11use crate::widget::{App, Widget};
12
13impl App {
14    /// Extend the active modal widget's path by hit-testing inside its
15    /// subtree at `pos_in_root`, so the modal's own children (buttons,
16    /// fields, scroll views) receive pointer events. Falls back to the
17    /// modal widget itself when nothing inside is hit (the scrim).
18    pub(crate) fn extend_modal_path(&self, modal_path: &[usize], pos_in_root: Point) -> Vec<usize> {
19        let mut widget: &dyn Widget = self.root.as_ref();
20        let mut pos = pos_in_root;
21        for &index in modal_path {
22            let Some(child) = widget.children().get(index) else {
23                return modal_path.to_vec();
24            };
25            let bounds = child.bounds();
26            if let Some(t) = widget.child_transform() {
27                t.inverse_transform(&mut pos.x, &mut pos.y);
28            }
29            pos = Point::new(pos.x - bounds.x, pos.y - bounds.y);
30            widget = child.as_ref();
31        }
32        let mut full = modal_path.to_vec();
33        if let Some(sub_path) = hit_test_subtree(widget, pos) {
34            full.extend(sub_path);
35        }
36        full
37    }
38
39    /// Mouse cursor moved. `screen_y` is Y-down physical pixels.
40    pub fn on_mouse_move(&mut self, screen_x: f64, screen_y: f64) {
41        // Reset cursor so the hovered widget can set it; Default if nothing sets it.
42        crate::cursor::reset_cursor_icon();
43        let screen = self.flip_y(screen_x, screen_y);
44        if crate::widgets::on_screen_keyboard::handle_software_keyboard_mouse_move(screen) {
45            self.drain_keyboard_synthetic_keys();
46            return;
47        }
48        let pos = super::keyboard_scroll::lift_to_world(screen);
49        set_current_mouse_world(pos);
50        if let Some(path) = active_modal_path(self.root.as_ref()) {
51            let path = self.extend_modal_path(&path, pos);
52            let event = Event::MouseMove { pos };
53            dispatch_event(&mut self.root, &path, &event, pos);
54            self.hovered = Some(path);
55            return;
56        }
57        self.dispatch_mouse_move(pos);
58    }
59
60    /// Mouse button pressed. `screen_y` is Y-down physical pixels.
61    pub fn on_mouse_down(
62        &mut self,
63        screen_x: f64,
64        screen_y: f64,
65        button: MouseButton,
66        mods: Modifiers,
67    ) {
68        let screen = self.flip_y(screen_x, screen_y);
69        // On-screen keyboard captures pointer events on its panel area
70        // before anything in the tree gets a look. Returning here also
71        // means the focused widget keeps focus (so the keyboard does
72        // not dismiss itself by stealing focus on every key tap).
73        if crate::widgets::on_screen_keyboard::handle_software_keyboard_mouse_down(
74            screen, button, mods,
75        ) {
76            return;
77        }
78        let pos = super::keyboard_scroll::lift_to_world(screen);
79        set_current_mouse_world(pos);
80        // A press hides any visible central tooltip and keeps it suppressed
81        // until the pointer leaves and re-enters a tipped widget.
82        crate::widgets::tooltip::controller::on_pointer_down();
83        // Count this press so widgets running their own multi-click gesture
84        // (Scene's background double-click) can detect an intervening press
85        // even when a hosted child consumes it before it can bubble.
86        crate::animation::bump_pointer_press_epoch();
87        let modal_path = active_modal_path(self.root.as_ref());
88        let event = Event::MouseDown {
89            pos,
90            button,
91            modifiers: mods,
92        };
93        if let Some(path) = modal_path {
94            // Hit-test inside the modal subtree so its children get the
95            // click, with the same click-to-focus rule as the normal path
96            // (text fields in dialogs need focus to type).
97            let path = self.extend_modal_path(&path, pos);
98            if widget_at_path(&mut self.root, &path).is_focusable() {
99                self.set_focus(Some(path.clone()));
100            } else {
101                self.set_focus(None);
102            }
103            if dispatch_event(&mut self.root, &path, &event, pos).is_consumed() {
104                self.captured = Some(path);
105            }
106            return;
107        }
108        let hit = self.compute_hit(pos);
109
110        // Click-to-focus: if the hit widget is focusable, give it focus.
111        if let Some(ref path) = hit {
112            let w = widget_at_path(&mut self.root, path);
113            if w.is_focusable() {
114                self.set_focus(Some(path.clone()));
115            } else {
116                self.set_focus(None);
117            }
118        } else {
119            self.set_focus(None);
120        }
121
122        if let Some(mut path) = hit {
123            let result = dispatch_event(&mut self.root, &path, &event, pos);
124            if result.is_consumed() {
125                self.maybe_bring_to_front(&mut path);
126                let capture_path = self.compute_hit(pos).unwrap_or(path);
127                self.captured = Some(capture_path);
128            }
129        }
130        // NO blanket request_draw.  Mouse-down on an inert area must not
131        // cause a repaint.  Each widget that changes visual state in
132        // response to a MouseDown (button press, window raise, focus
133        // indicator on the focus-gained widget, etc.) is responsible for
134        // calling `crate::animation::request_draw` itself.
135    }
136
137    /// Mouse button released. `screen_y` is Y-down.
138    pub fn on_mouse_up(
139        &mut self,
140        screen_x: f64,
141        screen_y: f64,
142        button: MouseButton,
143        mods: Modifiers,
144    ) {
145        let screen = self.flip_y(screen_x, screen_y);
146        // On-screen keyboard owns release events on its panel; releases
147        // here commit a key tap and synthesize a `KeyDown`. After
148        // consumption we drain the synthetic-key queue so the focused
149        // text widget receives the character in the same frame.
150        if crate::widgets::on_screen_keyboard::handle_software_keyboard_mouse_up(
151            screen, button, mods,
152        ) {
153            self.captured = None;
154            self.drain_keyboard_synthetic_keys();
155            return;
156        }
157        let pos = super::keyboard_scroll::lift_to_world(screen);
158        set_current_mouse_world(pos);
159        crate::widgets::tooltip::controller::on_pointer_up();
160        let event = Event::MouseUp {
161            pos,
162            button,
163            modifiers: mods,
164        };
165        if let Some(path) = active_modal_path(self.root.as_ref()) {
166            // Deliver the release where the press was captured (button
167            // click completion), falling back to the hit inside the modal.
168            let path = self
169                .captured
170                .take()
171                .unwrap_or_else(|| self.extend_modal_path(&path, pos));
172            dispatch_event(&mut self.root, &path, &event, pos);
173            return;
174        }
175        // Deliver release to captured widget first (if any), then clear capture.
176        if let Some(path) = self.captured.take() {
177            dispatch_event(&mut self.root, &path, &event, pos);
178        } else {
179            let hit = self.compute_hit(pos);
180            if let Some(path) = hit {
181                dispatch_event(&mut self.root, &path, &event, pos);
182            }
183        }
184    }
185}