Skip to main content

sequoia_input/
lib.rs

1#![forbid(unsafe_code)]
2
3use bitflags::bitflags;
4
5use sequoia_core::{Point, Vec2};
6
7bitflags! {
8    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9    pub struct Modifiers: u8 {
10        const NONE = 0;
11
12        const SHIFT = 1 << 0;
13
14        const CONTROL = 1 << 1;
15
16        const ALT = 1 << 2;
17
18        const SUPER = 1 << 3;
19    }
20}
21
22#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
23pub struct Key(u32);
24
25impl Key {
26    pub const NONE: Self = Self(0);
27
28    pub const TAB: Self = Self(1);
29
30    pub const ENTER: Self = Self(2);
31
32    pub const ESCAPE: Self = Self(3);
33
34    pub const SPACE: Self = Self(4);
35
36    pub const BACKSPACE: Self = Self(5);
37
38    pub const DELETE: Self = Self(6);
39
40    pub const ARROW_UP: Self = Self(7);
41
42    pub const ARROW_DOWN: Self = Self(8);
43
44    pub const ARROW_LEFT: Self = Self(9);
45
46    pub const ARROW_RIGHT: Self = Self(10);
47
48    pub const HOME: Self = Self(11);
49
50    pub const END: Self = Self(12);
51
52    pub const INSERT: Self = Self(13);
53
54    pub const PAGE_UP: Self = Self(14);
55
56    pub const PAGE_DOWN: Self = Self(15);
57
58    pub const F1: Self = Self(20);
59
60    pub const F2: Self = Self(21);
61
62    pub const F3: Self = Self(22);
63
64    pub const F4: Self = Self(23);
65
66    pub const F5: Self = Self(24);
67
68    pub const F6: Self = Self(25);
69
70    pub const F7: Self = Self(26);
71
72    pub const F8: Self = Self(27);
73
74    pub const F9: Self = Self(28);
75
76    pub const F10: Self = Self(29);
77
78    pub const F11: Self = Self(30);
79
80    pub const F12: Self = Self(31);
81
82    const CHAR_BASE: u32 = 0x1000;
83
84    pub fn from_char(c: char) -> Self {
85        Self(Self::CHAR_BASE + c as u32)
86    }
87
88    pub fn char(self) -> Option<char> {
89        if self.0 >= Self::CHAR_BASE {
90            char::from_u32(self.0 - Self::CHAR_BASE)
91        } else {
92            None
93        }
94    }
95
96    pub fn allows_repeat(self) -> bool {
97        if self.char().is_some() {
98            return true;
99        }
100        matches!(
101            self,
102            Self::SPACE
103                | Self::BACKSPACE
104                | Self::DELETE
105                | Self::ARROW_UP
106                | Self::ARROW_DOWN
107                | Self::ARROW_LEFT
108                | Self::ARROW_RIGHT
109                | Self::HOME
110                | Self::END
111                | Self::PAGE_UP
112                | Self::PAGE_DOWN
113                | Self::INSERT
114        )
115    }
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119#[non_exhaustive]
120pub enum PointerButton {
121    Left,
122
123    Right,
124
125    Middle,
126}
127
128#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
129#[non_exhaustive]
130pub enum PenTool {
131    #[default]
132    Mouse,
133
134    Pen,
135
136    Eraser,
137
138    Touch,
139
140    Unknown,
141}
142
143#[derive(Clone, Copy, Debug, PartialEq)]
144#[non_exhaustive]
145pub enum PointerEvent {
146    Move {
147        pos: Point,
148        modifiers: Modifiers,
149    },
150    Down {
151        button: PointerButton,
152        pos: Point,
153        modifiers: Modifiers,
154    },
155    Up {
156        button: PointerButton,
157        pos: Point,
158        modifiers: Modifiers,
159    },
160    Cancel,
161    Wheel {
162        delta: Vec2,
163        pos: Point,
164        modifiers: Modifiers,
165    },
166
167    PenMove {
168        pos: Point,
169        pressure: f32,
170        tilt: Vec2,
171        tool: PenTool,
172        modifiers: Modifiers,
173    },
174    PenDown {
175        button: PointerButton,
176        pos: Point,
177        pressure: f32,
178        tilt: Vec2,
179        tool: PenTool,
180        modifiers: Modifiers,
181    },
182    PenUp {
183        button: PointerButton,
184        pos: Point,
185        pressure: f32,
186        modifiers: Modifiers,
187    },
188
189    TouchDown {
190        id: u64,
191        pos: Point,
192        force: Option<f32>,
193        modifiers: Modifiers,
194    },
195    TouchMove {
196        id: u64,
197        pos: Point,
198        force: Option<f32>,
199        modifiers: Modifiers,
200    },
201    TouchUp {
202        id: u64,
203        pos: Point,
204        modifiers: Modifiers,
205    },
206
207    Pinch {
208        delta: f32,
209        pos: Point,
210        modifiers: Modifiers,
211    },
212}
213
214impl PointerEvent {
215    pub fn pos(self) -> Point {
216        match self {
217            Self::Move { pos, .. }
218            | Self::Down { pos, .. }
219            | Self::Up { pos, .. }
220            | Self::Wheel { pos, .. }
221            | Self::PenMove { pos, .. }
222            | Self::PenDown { pos, .. }
223            | Self::PenUp { pos, .. }
224            | Self::TouchDown { pos, .. }
225            | Self::TouchMove { pos, .. }
226            | Self::TouchUp { pos, .. }
227            | Self::Pinch { pos, .. } => pos,
228            Self::Cancel => Point::ZERO,
229        }
230    }
231
232    pub fn pressure(self) -> f32 {
233        match self {
234            Self::PenMove { pressure, .. }
235            | Self::PenDown { pressure, .. }
236            | Self::PenUp { pressure, .. } => pressure.clamp(0.0, 1.0),
237            Self::TouchDown { force, .. } | Self::TouchMove { force, .. } => {
238                force.unwrap_or(1.0).clamp(0.0, 1.0)
239            }
240            Self::Cancel => 0.0,
241            _ => 1.0,
242        }
243    }
244
245    pub fn tool(self) -> PenTool {
246        match self {
247            Self::PenMove { tool, .. } | Self::PenDown { tool, .. } => tool,
248            Self::PenUp { .. } => PenTool::Pen,
249            Self::TouchDown { .. } | Self::TouchMove { .. } | Self::TouchUp { .. } => {
250                PenTool::Touch
251            }
252            _ => PenTool::Mouse,
253        }
254    }
255
256    pub fn touch_id(self) -> Option<u64> {
257        match self {
258            Self::TouchDown { id, .. } | Self::TouchMove { id, .. } | Self::TouchUp { id, .. } => {
259                Some(id)
260            }
261            _ => None,
262        }
263    }
264
265    pub fn modifiers(self) -> Modifiers {
266        match self {
267            Self::Move { modifiers, .. }
268            | Self::Down { modifiers, .. }
269            | Self::Up { modifiers, .. }
270            | Self::Wheel { modifiers, .. }
271            | Self::PenMove { modifiers, .. }
272            | Self::PenDown { modifiers, .. }
273            | Self::PenUp { modifiers, .. }
274            | Self::TouchDown { modifiers, .. }
275            | Self::TouchMove { modifiers, .. }
276            | Self::TouchUp { modifiers, .. }
277            | Self::Pinch { modifiers, .. } => modifiers,
278            Self::Cancel => Modifiers::NONE,
279        }
280    }
281}
282
283#[derive(Clone, Copy, Debug, PartialEq)]
284pub struct KeyEvent {
285    pub key: Key,
286
287    pub modifiers: Modifiers,
288}
289
290/// Platform input-method events.
291///
292/// A composition starts with [`ImeEvent::Preedit`], may update the preedit text
293/// several times, and ends with [`ImeEvent::Commit`] (text was accepted) or
294/// [`ImeEvent::Disabled`] (composition was cancelled). Hosts that only care
295/// about the final text can handle `Commit` alone; text widgets show the
296/// preedit while it lasts and write it to the model on commit.
297#[derive(Clone, Debug, PartialEq)]
298#[non_exhaustive]
299pub enum ImeEvent {
300    /// The input method is now active for the focused text input.
301    Enabled,
302
303    /// Composition in progress. `value` replaces the current selection (or
304    /// sits at the caret when nothing is selected) and is not committed yet.
305    ///
306    /// `cursor` is an optional byte range inside `value`
307    /// `(cursor, selection_end)`, in the units winit reports. It lets the
308    /// widget place the caret inside the preedit, which CJK input relies on
309    /// while the composition is being converted.
310    Preedit {
311        value: String,
312
313        cursor: Option<(usize, usize)>,
314    },
315
316    /// Final text for the current composition. It replaces the selection the
317    /// composition started with, so a commit after selecting text swaps the
318    /// selection rather than inserting next to it.
319    Commit(String),
320
321    /// The input method lost focus or the composition was cancelled.
322    Disabled,
323}
324
325impl ImeEvent {
326    /// Whether this event leaves a composition visible.
327    pub fn is_composing(&self) -> bool {
328        matches!(self, Self::Preedit { value, .. } if !value.is_empty())
329    }
330}
331
332#[derive(Clone, Debug, PartialEq)]
333#[non_exhaustive]
334pub enum Event {
335    Pointer(PointerEvent),
336
337    Key(KeyEvent),
338
339    Ime(ImeEvent),
340
341    FilesHovered {
342        paths: Vec<std::path::PathBuf>,
343        position: Point,
344    },
345
346    FilesDropped {
347        paths: Vec<std::path::PathBuf>,
348        position: Point,
349    },
350
351    FilesCancelled,
352}
353
354#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
355#[non_exhaustive]
356pub enum DropEffect {
357    None,
358
359    #[default]
360    Move,
361
362    Copy,
363
364    Link,
365}
366
367pub fn effect_for_modifiers(mods: Modifiers) -> DropEffect {
368    if mods.contains(Modifiers::CONTROL) {
369        DropEffect::Copy
370    } else if mods.contains(Modifiers::ALT) {
371        DropEffect::Link
372    } else {
373        DropEffect::Move
374    }
375}
376
377#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
378#[non_exhaustive]
379pub enum CursorIcon {
380    #[default]
381    Default,
382
383    Pointer,
384
385    Grab,
386
387    Grabbing,
388
389    Text,
390
391    ColResize,
392
393    RowResize,
394
395    Crosshair,
396}
397
398#[derive(Clone, Copy, Debug, PartialEq, Eq)]
399pub enum EventPhase {
400    Capture,
401
402    Target,
403
404    Bubble,
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410
411    #[test]
412    fn key_char_roundtrip() {
413        let k = Key::from_char('a');
414        assert_eq!(k.char(), Some('a'));
415        assert_ne!(k, Key::from_char('b'));
416        assert_eq!(Key::TAB.char(), None);
417    }
418
419    #[test]
420    fn modifiers_ops() {
421        let m = Modifiers::SHIFT | Modifiers::CONTROL;
422        assert!(m.contains(Modifiers::SHIFT));
423        assert!(!m.contains(Modifiers::ALT));
424        assert_eq!(Modifiers::default(), Modifiers::NONE);
425    }
426
427    #[test]
428    fn drop_effect_for_modifiers() {
429        assert_eq!(effect_for_modifiers(Modifiers::NONE), DropEffect::Move);
430        assert_eq!(effect_for_modifiers(Modifiers::CONTROL), DropEffect::Copy);
431        assert_eq!(effect_for_modifiers(Modifiers::ALT), DropEffect::Link);
432
433        assert_eq!(
434            effect_for_modifiers(Modifiers::CONTROL | Modifiers::ALT),
435            DropEffect::Copy
436        );
437    }
438}