agg_gui/event.rs
1//! Event types for the widget system.
2//!
3//! All coordinates in events are **first-quadrant (Y-up)** by the time any
4//! widget code sees them. The single Y-down → Y-up conversion happens at the
5//! platform boundary inside [`crate::widget::App`].
6
7use crate::geometry::Point;
8use crate::touch_state::MultiTouchInfo;
9
10/// Which mouse button triggered a `MouseDown` or `MouseUp` event.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum MouseButton {
13 Left,
14 Middle,
15 Right,
16 Other(u8),
17}
18
19/// Modifier keys held at the time of an event.
20///
21/// `meta` is the platform-specific "super" key: **Cmd** on macOS, **Super /
22/// Windows key** on Linux, **Windows key** on Windows. Widgets that want
23/// portable command shortcuts should use the runtime platform helpers rather
24/// than treating `ctrl || meta` as universally equivalent.
25#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
26pub struct Modifiers {
27 pub shift: bool,
28 pub ctrl: bool,
29 pub alt: bool,
30 pub meta: bool,
31}
32
33/// A logical keyboard key.
34#[derive(Clone, Debug, PartialEq)]
35pub enum Key {
36 /// A printable character, already translated through the keyboard layout.
37 Char(char),
38 Backspace,
39 Delete,
40 /// The `Insert` key. Paired with `Shift`/`Ctrl` for classic Windows
41 /// clipboard shortcuts (`Shift+Ins` paste, `Ctrl+Ins` copy).
42 Insert,
43 ArrowLeft,
44 ArrowRight,
45 ArrowUp,
46 ArrowDown,
47 Home,
48 End,
49 /// Page-up / page-down — scroll the caret by one viewport height of lines
50 /// in a multiline editor.
51 PageUp,
52 PageDown,
53 Tab,
54 Enter,
55 Escape,
56 /// Any key not in the above set — not usually handled, included for
57 /// completeness.
58 Other(String),
59}
60
61/// A GUI event delivered to a widget.
62///
63/// Coordinate positions are in the **local** coordinate space of the widget
64/// receiving the event (bottom-left origin, Y-up). The framework translates
65/// positions as it descends the widget tree.
66#[derive(Clone, Debug)]
67pub enum Event {
68 /// The cursor moved to `pos` (may be outside widget bounds — used to
69 /// clear hover state).
70 MouseMove { pos: Point },
71 /// A mouse button was pressed at `pos`.
72 MouseDown {
73 pos: Point,
74 button: MouseButton,
75 modifiers: Modifiers,
76 },
77 /// A mouse button was released at `pos`.
78 MouseUp {
79 pos: Point,
80 button: MouseButton,
81 modifiers: Modifiers,
82 },
83 /// A key was pressed while this widget (or a descendant) had focus.
84 KeyDown { key: Key, modifiers: Modifiers },
85 /// A key was released.
86 KeyUp { key: Key, modifiers: Modifiers },
87 /// Sent by the framework when this widget gains keyboard focus.
88 FocusGained,
89 /// Sent by the framework when this widget loses keyboard focus.
90 FocusLost,
91 /// Mouse wheel scrolled. Convention matches `winit` /
92 /// `WheelEvent` after the OS applies its natural-scroll
93 /// preference: **positive `delta_y` means the user wants to see
94 /// content ABOVE the current view** (wheel rotated forward on
95 /// Windows / wheel forward + natural-scroll on macOS). Scroll
96 /// containers should DECREASE their scroll offset when `delta_y`
97 /// is positive. `delta_x` follows the same sign rule for
98 /// horizontal scroll (positive = see content to the LEFT).
99 /// Magnitude is in logical pixels; line deltas should be
100 /// pre-scaled by the platform shell (~40 px per line).
101 MouseWheel {
102 pos: Point,
103 delta_y: f64,
104 delta_x: f64,
105 modifiers: Modifiers,
106 },
107 /// One or more files were dropped onto the window at `pos`.
108 ///
109 /// `paths` is non-empty. Native windowing layers (winit) typically
110 /// emit one path per `WindowEvent::DroppedFile` — the framework
111 /// either forwards each as its own `FileDropped` event, or batches
112 /// drops within a single gesture into one event. Receivers should
113 /// not rely on batching behaviour: handle each path in the vec.
114 ///
115 /// Coordinates follow the same convention as `MouseMove`/`MouseDown`:
116 /// widget-local Y-up. The cursor lives at `pos` at the moment of
117 /// drop, so widgets can spawn objects under the user's intent.
118 FileDropped {
119 pos: Point,
120 paths: Vec<std::path::PathBuf>,
121 },
122 /// A two-or-more-finger touch gesture is active this frame.
123 ///
124 /// Routed like a captured pointer: on the frame the gesture begins,
125 /// the framework hit-tests [`MultiTouchInfo::center_pos`] down the
126 /// tree and delivers to the deepest widget that consumes it; that
127 /// widget then receives every subsequent frame's aggregate until the
128 /// gesture ends, even if the centroid drifts outside its bounds
129 /// (standard capture semantics). Widgets accumulate the per-frame
130 /// `zoom_delta` / `rotation_delta` / `translation_delta` into their
131 /// own transform state.
132 ///
133 /// `info.center_pos` is translated into the receiving widget's local
134 /// Y-up space as the event descends, exactly like a mouse `pos`. The
135 /// deltas are displacement vectors and are **not** translated — they
136 /// are the same in every coordinate frame.
137 MultiTouch { info: MultiTouchInfo },
138}
139
140/// What a widget returns from [`crate::widget::Widget::on_event`].
141///
142/// # Automatic invalidation
143///
144/// The framework's event dispatcher (see [`crate::widget::tree`]) treats a
145/// [`Consumed`](EventResult::Consumed) result as "this widget changed
146/// something visible" and schedules a repaint via
147/// [`crate::animation::request_draw`] on the widget's behalf. This makes the
148/// correct default automatic: the most common framework bug is a new widget
149/// that mutates paint-affecting state on an event but forgets to request a
150/// draw, so parts of it don't repaint. With auto-invalidation a plain
151/// `Consumed` is always safe.
152///
153/// A widget that consumes a *high-frequency* event (typically `MouseMove`)
154/// **without** any visual change — for example, a hover affordance that only
155/// updates the OS cursor — should return
156/// [`ConsumedQuiet`](EventResult::ConsumedQuiet) so it doesn't schedule a
157/// wasteful repaint on every event. `ConsumedQuiet` still stops propagation
158/// exactly like `Consumed`; it only suppresses the automatic draw request.
159#[derive(Clone, Copy, Debug, PartialEq, Eq)]
160pub enum EventResult {
161 /// The widget handled the event and may have changed its appearance;
162 /// stop propagation and schedule a repaint automatically.
163 Consumed,
164 /// The widget handled the event (stop propagation) but produced **no**
165 /// visual change, so the dispatcher must NOT schedule a repaint. Use
166 /// this only for genuinely quiet consumption of high-frequency events.
167 ConsumedQuiet,
168 /// The widget did not handle the event; continue bubbling up.
169 Ignored,
170}
171
172impl EventResult {
173 /// `true` for both [`Consumed`](EventResult::Consumed) and
174 /// [`ConsumedQuiet`](EventResult::ConsumedQuiet).
175 ///
176 /// Propagation, capture, and focus logic should branch on this rather
177 /// than comparing against `Consumed` directly, so a quietly-consuming
178 /// widget still stops the event exactly like a loud one.
179 pub const fn is_consumed(self) -> bool {
180 matches!(self, EventResult::Consumed | EventResult::ConsumedQuiet)
181 }
182
183 /// `true` only for [`Consumed`](EventResult::Consumed) — i.e. the
184 /// dispatcher should call [`crate::animation::request_draw`] for this
185 /// result. `ConsumedQuiet` and `Ignored` return `false`.
186 pub const fn requests_redraw(self) -> bool {
187 matches!(self, EventResult::Consumed)
188 }
189}