Skip to main content

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}