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