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