fission_core/event.rs
1//! Input events consumed by the [`Runtime`](crate::Runtime).
2//!
3//! Platform shells convert native OS events into the types defined here and
4//! pass them to [`Runtime::handle_input`](crate::Runtime::handle_input).
5
6use fission_layout::{LayoutPoint, LayoutSize};
7use serde::{Deserialize, Serialize};
8
9/// Stable identity for one active pointer contact.
10///
11/// Pointer ids are only required to be unique among simultaneously active
12/// pointers. Mouse input uses [`PointerId::MOUSE`]; touch ids come from the
13/// platform shell.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub struct PointerId(pub u128);
16
17impl PointerId {
18 pub const MOUSE: Self = Self(0);
19
20 /// Namespaces a platform contact id away from the singleton mouse id.
21 pub const fn contact(platform_id: u64) -> Self {
22 Self(platform_id as u128 + 1)
23 }
24}
25
26impl Default for PointerId {
27 fn default() -> Self {
28 Self::MOUSE
29 }
30}
31
32/// Physical input source which produced a pointer event.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum PointerKind {
35 Mouse,
36 Touch,
37 Stylus,
38 Unknown,
39}
40
41impl Default for PointerKind {
42 fn default() -> Self {
43 Self::Mouse
44 }
45}
46
47/// Lifecycle phase shared by continuous pointer signals.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
49pub enum PointerPhase {
50 Started,
51 Moved,
52 Ended,
53 Cancelled,
54}
55
56impl Default for PointerPhase {
57 fn default() -> Self {
58 Self::Moved
59 }
60}
61
62/// Units reported by a scroll input source.
63///
64/// This deliberately describes the reported delta rather than guessing whether
65/// a high-resolution device is a wheel or trackpad.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub enum ScrollDeltaMode {
68 Line,
69 Pixel,
70}
71
72impl Default for ScrollDeltaMode {
73 fn default() -> Self {
74 Self::Pixel
75 }
76}
77
78/// Identifies which mouse button or touch produced a pointer event.
79///
80/// # Variants
81///
82/// - `Primary` -- left mouse button or primary touch contact.
83/// - `Secondary` -- right mouse button.
84/// - `Middle` -- middle mouse button (scroll wheel click).
85/// - `Other(u8)` -- auxiliary buttons (back, forward, etc.).
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87pub enum PointerButton {
88 /// Left mouse button or primary touch.
89 Primary,
90 /// Right mouse button.
91 Secondary,
92 /// Middle mouse button.
93 Middle,
94 /// Auxiliary buttons identified by index.
95 Other(u8),
96}
97
98/// A pointer (mouse / touch / stylus) event in layout coordinates.
99///
100/// # Example
101///
102/// ```rust,ignore
103/// let event = InputEvent::Pointer(PointerEvent::Down {
104/// pointer_id: PointerId::MOUSE,
105/// kind: PointerKind::Mouse,
106/// point: LayoutPoint::new(100.0, 200.0),
107/// button: PointerButton::Primary,
108/// modifiers: 0,
109/// });
110/// runtime.handle_input(event, &ir, &layout)?;
111/// ```
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub enum PointerEvent {
114 /// A button was pressed at the given point.
115 Down {
116 pointer_id: PointerId,
117 kind: PointerKind,
118 point: LayoutPoint,
119 button: PointerButton,
120 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
121 modifiers: u8,
122 },
123 /// A button was released at the given point.
124 Up {
125 pointer_id: PointerId,
126 kind: PointerKind,
127 point: LayoutPoint,
128 button: PointerButton,
129 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
130 modifiers: u8,
131 },
132 /// The pointer moved (no button state change).
133 Move {
134 pointer_id: PointerId,
135 kind: PointerKind,
136 point: LayoutPoint,
137 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
138 modifiers: u8,
139 },
140 /// An active pointer sequence was cancelled by the platform.
141 Cancel {
142 pointer_id: PointerId,
143 kind: PointerKind,
144 point: LayoutPoint,
145 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
146 modifiers: u8,
147 },
148 /// A scroll (mouse wheel or trackpad) gesture.
149 Scroll {
150 point: LayoutPoint,
151 /// Scroll delta in layout units (positive = scroll down / right).
152 delta: LayoutPoint,
153 /// Whether the platform supplied line or pixel deltas.
154 delta_mode: ScrollDeltaMode,
155 /// Lifecycle phase supplied by the platform.
156 phase: PointerPhase,
157 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
158 modifiers: u8,
159 },
160 /// A platform-recognized magnification gesture, such as a trackpad pinch.
161 Magnify {
162 /// Gesture focal point in layout coordinates.
163 point: LayoutPoint,
164 /// Multiplicative scale factor for this update (`1.0` means unchanged).
165 scale_factor: f32,
166 phase: PointerPhase,
167 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
168 modifiers: u8,
169 },
170}
171
172/// Platform-independent key code for keyboard events.
173///
174/// Named keys map directly to their function. Printable characters use
175/// `Char(char)`.
176///
177/// # Example
178///
179/// ```rust,ignore
180/// let event = InputEvent::Keyboard(KeyEvent::Down {
181/// key_code: KeyCode::Enter,
182/// modifiers: 0,
183/// });
184/// ```
185#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub enum KeyCode {
187 Space,
188 Enter,
189 Escape,
190 Backspace,
191 Delete,
192 Tab,
193 Left,
194 Right,
195 Up,
196 Down,
197 Home,
198 End,
199 PageUp,
200 PageDown,
201 /// A printable character.
202 Char(char),
203}
204
205/// Shift modifier bit.
206pub const MOD_SHIFT: u8 = 1;
207/// Alt/Option modifier bit.
208pub const MOD_ALT: u8 = 2;
209/// Control modifier bit.
210pub const MOD_CTRL: u8 = 4;
211/// Super/Meta/Command modifier bit.
212pub const MOD_SUPER: u8 = 8;
213
214/// A keyboard key press or release event.
215///
216/// The `modifiers` field is a bitmask: bit 0 = Shift, bit 1 = Alt,
217/// bit 2 = Ctrl, bit 3 = Super/Meta.
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
219pub enum KeyEvent {
220 /// A key was pressed.
221 Down {
222 key_code: KeyCode,
223 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
224 modifiers: u8,
225 },
226 /// A key press carrying the complete text produced by the platform.
227 ///
228 /// `key_code` remains the logical shortcut/navigation identity. `text` is
229 /// inserted as one edit and may contain several scalars or graphemes.
230 DownWithText {
231 key_code: KeyCode,
232 modifiers: u8,
233 text: String,
234 },
235 /// A key was released.
236 Up { key_code: KeyCode, modifiers: u8 },
237}
238
239/// Semantic text-editing commands produced by a platform shell.
240///
241/// Browser shells should translate trusted `copy`, `cut`, and `paste` events
242/// into these commands instead of synthesizing platform-specific key chords.
243#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
244pub enum EditingCommand {
245 Copy,
246 Cut,
247 Paste(String),
248 SelectAll,
249 Undo,
250 Redo,
251}
252
253/// Application lifecycle events.
254#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
255pub enum LifecycleEvent {
256 /// The application has finished initialisation.
257 Init,
258 /// The application returned to the foreground.
259 Resume,
260 /// The application moved to the background.
261 Pause,
262 /// The application is about to terminate.
263 Terminate,
264 /// The viewport was resized.
265 Resize { size: LayoutSize },
266}
267
268/// High-level gesture events recognised by the platform.
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub enum GestureEvent {
271 /// A single tap (pointer down + up within threshold).
272 Tap { point: LayoutPoint },
273 /// Two taps in quick succession.
274 DoubleTap { point: LayoutPoint },
275 /// A pan/drag gesture began.
276 PanStart { point: LayoutPoint },
277 /// A pan/drag gesture updated.
278 PanUpdate {
279 point: LayoutPoint,
280 delta: LayoutPoint,
281 },
282 /// A pan/drag gesture ended.
283 PanEnd { point: LayoutPoint },
284 /// The pointer was held down for longer than the long-press threshold.
285 LongPress { point: LayoutPoint },
286}
287
288/// File drag-and-drop events delivered by desktop shells.
289///
290/// These events model OS-level drags such as files dragged from Finder,
291/// Explorer, or a Linux file manager into a Fission window. Internal widget
292/// drags use normal pointer events plus the widget drag payload.
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
294pub enum ExternalDragEvent {
295 /// One or more external files are hovering over the window.
296 Hover {
297 point: LayoutPoint,
298 paths: Vec<String>,
299 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
300 modifiers: u8,
301 },
302 /// The external drag left the window or was cancelled by the platform.
303 Cancel,
304 /// One or more external files were dropped at the current pointer point.
305 Drop {
306 point: LayoutPoint,
307 paths: Vec<String>,
308 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
309 modifiers: u8,
310 },
311}
312
313/// The top-level input event type consumed by
314/// [`Runtime::handle_input`](crate::Runtime::handle_input).
315///
316/// Platform shells convert native OS events into `InputEvent` values.
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
318pub enum InputEvent {
319 /// Mouse, touch, or stylus events.
320 Pointer(PointerEvent),
321 /// Keyboard key events.
322 Keyboard(KeyEvent),
323 /// Input Method Editor (IME) events for CJK and composed text.
324 Ime(ImeEvent),
325 /// A platform-native semantic text-editing command.
326 Editing(EditingCommand),
327 /// A complete platform-neutral value, selection, composition, or range edit.
328 TextEdit(crate::TextEditCommand),
329 /// A platform requested Fission's contextual action at this position.
330 ContextMenuRequested {
331 point: LayoutPoint,
332 /// Modifier bitmask (Shift=1, Alt=2, Ctrl=4, Super=8).
333 modifiers: u8,
334 },
335 /// High-level gesture events.
336 Gesture(GestureEvent),
337 /// Desktop shell drag-and-drop events from outside the app.
338 ExternalDrag(ExternalDragEvent),
339 /// Application lifecycle transitions.
340 Lifecycle(LifecycleEvent),
341}
342
343/// Input Method Editor events for composed text input (CJK, emoji, etc.).
344#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
345pub enum ImeEvent {
346 /// The IME is composing text before the user confirms it.
347 ///
348 /// `cursor` is an optional byte range inside `text` reported by the
349 /// platform IME. Shells can use it to render the active composition cursor
350 /// or marked segment separately from the rest of the preedit text.
351 Preedit {
352 text: String,
353 cursor: Option<(usize, usize)>,
354 },
355 /// The active composition was cancelled without committing text.
356 Cancel,
357 /// The user confirmed the composed text.
358 Commit { text: String },
359}
360
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 #[test]
366 fn pointer_contact_identity_round_trips() {
367 let event = PointerEvent::Cancel {
368 pointer_id: PointerId::contact(42),
369 kind: PointerKind::Touch,
370 point: LayoutPoint::new(12.0, 18.0),
371 modifiers: MOD_SHIFT,
372 };
373 let encoded = serde_json::to_string(&event).unwrap();
374 assert_eq!(
375 serde_json::from_str::<PointerEvent>(&encoded).unwrap(),
376 event
377 );
378 }
379
380 #[test]
381 fn detailed_pointer_signals_round_trip() {
382 let scroll = PointerEvent::Scroll {
383 point: LayoutPoint::new(3.0, 5.0),
384 delta: LayoutPoint::new(-2.0, 7.0),
385 delta_mode: ScrollDeltaMode::Line,
386 phase: PointerPhase::Started,
387 modifiers: MOD_CTRL,
388 };
389 let magnify = PointerEvent::Magnify {
390 point: LayoutPoint::new(9.0, 11.0),
391 scale_factor: 1.25,
392 phase: PointerPhase::Moved,
393 modifiers: 0,
394 };
395 for event in [scroll, magnify] {
396 let encoded = serde_json::to_string(&event).unwrap();
397 assert_eq!(
398 serde_json::from_str::<PointerEvent>(&encoded).unwrap(),
399 event
400 );
401 }
402 }
403}