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}