Skip to main content

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}