Skip to main content

fret_runtime/
input.rs

1use crate::PlatformCapabilities;
2use crate::WindowInputArbitrationSnapshot;
3use crate::WindowPointerOcclusion;
4use fret_core::{KeyCode, Modifiers};
5use std::borrow::Cow;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Platform {
9    Macos,
10    Windows,
11    Linux,
12    Web,
13}
14
15impl Platform {
16    pub fn current() -> Self {
17        #[cfg(target_os = "macos")]
18        return Self::Macos;
19        #[cfg(target_os = "windows")]
20        return Self::Windows;
21        #[cfg(all(unix, not(target_os = "macos")))]
22        return Self::Linux;
23        #[cfg(target_arch = "wasm32")]
24        return Self::Web;
25    }
26
27    pub fn as_str(self) -> &'static str {
28        match self {
29            Self::Macos => "macos",
30            Self::Windows => "windows",
31            Self::Linux => "linux",
32            Self::Web => "web",
33        }
34    }
35}
36
37impl Default for Platform {
38    fn default() -> Self {
39        Self::current()
40    }
41}
42
43#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
44pub enum TextBoundaryMode {
45    #[default]
46    UnicodeWord,
47    Identifier,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct InputContext {
52    pub platform: Platform,
53    pub caps: PlatformCapabilities,
54    pub ui_has_modal: bool,
55    /// Window-level input arbitration snapshot for the current dispatch pass.
56    ///
57    /// When present, this allows policy-heavy ecosystem layers to observe modal/capture/occlusion
58    /// state without reaching into global services (and keeps the snapshot consistent within a
59    /// single dispatch).
60    pub window_arbitration: Option<WindowInputArbitrationSnapshot>,
61    pub focus_is_text_input: bool,
62    pub text_boundary_mode: TextBoundaryMode,
63    pub edit_can_undo: bool,
64    pub edit_can_redo: bool,
65    pub router_can_back: bool,
66    pub router_can_forward: bool,
67    pub dispatch_phase: InputDispatchPhase,
68}
69
70impl InputContext {
71    pub fn fallback(platform: Platform, caps: PlatformCapabilities) -> Self {
72        Self {
73            platform,
74            caps,
75            ..Default::default()
76        }
77    }
78}
79
80impl Default for InputContext {
81    fn default() -> Self {
82        Self {
83            platform: Platform::current(),
84            caps: PlatformCapabilities::default(),
85            ui_has_modal: false,
86            window_arbitration: None,
87            focus_is_text_input: false,
88            text_boundary_mode: TextBoundaryMode::UnicodeWord,
89            edit_can_undo: true,
90            edit_can_redo: true,
91            router_can_back: false,
92            router_can_forward: false,
93            dispatch_phase: InputDispatchPhase::Bubble,
94        }
95    }
96}
97
98impl InputContext {
99    pub fn window_arbitration(&self) -> Option<WindowInputArbitrationSnapshot> {
100        self.window_arbitration
101    }
102
103    pub fn window_pointer_occlusion(&self) -> WindowPointerOcclusion {
104        self.window_arbitration
105            .map(|snapshot| snapshot.pointer_occlusion)
106            .unwrap_or_default()
107    }
108}
109
110#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
111pub enum InputDispatchPhase {
112    #[default]
113    Bubble,
114    Preview,
115    Capture,
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
119pub enum DefaultAction {
120    FocusOnPointerDown,
121}
122
123#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
124pub struct DefaultActionSet(u32);
125
126impl DefaultActionSet {
127    pub fn insert(&mut self, action: DefaultAction) {
128        self.0 |= 1 << (action as u32);
129    }
130
131    pub fn contains(self, action: DefaultAction) -> bool {
132        (self.0 & (1 << (action as u32))) != 0
133    }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137pub struct KeyChord {
138    pub key: KeyCode,
139    pub mods: Modifiers,
140}
141
142impl KeyChord {
143    pub fn new(key: KeyCode, mods: Modifiers) -> Self {
144        Self { key, mods }
145    }
146}
147
148pub fn format_chord(platform: Platform, chord: KeyChord) -> String {
149    let mut parts: Vec<&'static str> = Vec::new();
150
151    match platform {
152        Platform::Macos => {
153            if chord.mods.alt_gr {
154                parts.push("AltGr");
155            }
156            if chord.mods.meta {
157                parts.push("Cmd");
158            }
159            if chord.mods.ctrl {
160                parts.push("Ctrl");
161            }
162            if chord.mods.alt {
163                parts.push("Alt");
164            }
165            if chord.mods.shift {
166                parts.push("Shift");
167            }
168        }
169        Platform::Windows | Platform::Linux | Platform::Web => {
170            if chord.mods.ctrl {
171                parts.push("Ctrl");
172            }
173            if chord.mods.alt_gr {
174                parts.push("AltGr");
175            }
176            if chord.mods.alt {
177                parts.push("Alt");
178            }
179            if chord.mods.shift {
180                parts.push("Shift");
181            }
182            if chord.mods.meta {
183                parts.push("Meta");
184            }
185        }
186    }
187
188    let key = key_label(chord.key);
189    if parts.is_empty() {
190        return key.into_owned();
191    }
192    format!("{}+{}", parts.join("+"), key)
193}
194
195pub fn format_sequence(platform: Platform, sequence: &[KeyChord]) -> String {
196    let mut out = String::new();
197    for (index, chord) in sequence.iter().copied().enumerate() {
198        if index > 0 {
199            out.push(' ');
200        }
201        out.push_str(&format_chord(platform, chord));
202    }
203    out
204}
205
206fn key_label(key: KeyCode) -> Cow<'static, str> {
207    match key {
208        KeyCode::Escape => Cow::Borrowed("Esc"),
209        KeyCode::Enter => Cow::Borrowed("Enter"),
210        KeyCode::Tab => Cow::Borrowed("Tab"),
211        KeyCode::Backspace => Cow::Borrowed("Backspace"),
212        KeyCode::Space => Cow::Borrowed("Space"),
213
214        KeyCode::ArrowUp => Cow::Borrowed("Up"),
215        KeyCode::ArrowDown => Cow::Borrowed("Down"),
216        KeyCode::ArrowLeft => Cow::Borrowed("Left"),
217        KeyCode::ArrowRight => Cow::Borrowed("Right"),
218
219        KeyCode::Home => Cow::Borrowed("Home"),
220        KeyCode::End => Cow::Borrowed("End"),
221        KeyCode::PageUp => Cow::Borrowed("PageUp"),
222        KeyCode::PageDown => Cow::Borrowed("PageDown"),
223        KeyCode::Insert => Cow::Borrowed("Insert"),
224        KeyCode::Delete => Cow::Borrowed("Delete"),
225
226        KeyCode::CapsLock => Cow::Borrowed("CapsLock"),
227
228        KeyCode::ShiftLeft | KeyCode::ShiftRight => Cow::Borrowed("Shift"),
229        KeyCode::ControlLeft | KeyCode::ControlRight => Cow::Borrowed("Ctrl"),
230        KeyCode::AltLeft | KeyCode::AltRight => Cow::Borrowed("Alt"),
231        KeyCode::MetaLeft | KeyCode::MetaRight => Cow::Borrowed("Super"),
232
233        KeyCode::Digit0 => Cow::Borrowed("0"),
234        KeyCode::Digit1 => Cow::Borrowed("1"),
235        KeyCode::Digit2 => Cow::Borrowed("2"),
236        KeyCode::Digit3 => Cow::Borrowed("3"),
237        KeyCode::Digit4 => Cow::Borrowed("4"),
238        KeyCode::Digit5 => Cow::Borrowed("5"),
239        KeyCode::Digit6 => Cow::Borrowed("6"),
240        KeyCode::Digit7 => Cow::Borrowed("7"),
241        KeyCode::Digit8 => Cow::Borrowed("8"),
242        KeyCode::Digit9 => Cow::Borrowed("9"),
243
244        KeyCode::KeyA => Cow::Borrowed("A"),
245        KeyCode::KeyB => Cow::Borrowed("B"),
246        KeyCode::KeyC => Cow::Borrowed("C"),
247        KeyCode::KeyD => Cow::Borrowed("D"),
248        KeyCode::KeyE => Cow::Borrowed("E"),
249        KeyCode::KeyF => Cow::Borrowed("F"),
250        KeyCode::KeyG => Cow::Borrowed("G"),
251        KeyCode::KeyH => Cow::Borrowed("H"),
252        KeyCode::KeyI => Cow::Borrowed("I"),
253        KeyCode::KeyJ => Cow::Borrowed("J"),
254        KeyCode::KeyK => Cow::Borrowed("K"),
255        KeyCode::KeyL => Cow::Borrowed("L"),
256        KeyCode::KeyM => Cow::Borrowed("M"),
257        KeyCode::KeyN => Cow::Borrowed("N"),
258        KeyCode::KeyO => Cow::Borrowed("O"),
259        KeyCode::KeyP => Cow::Borrowed("P"),
260        KeyCode::KeyQ => Cow::Borrowed("Q"),
261        KeyCode::KeyR => Cow::Borrowed("R"),
262        KeyCode::KeyS => Cow::Borrowed("S"),
263        KeyCode::KeyT => Cow::Borrowed("T"),
264        KeyCode::KeyU => Cow::Borrowed("U"),
265        KeyCode::KeyV => Cow::Borrowed("V"),
266        KeyCode::KeyW => Cow::Borrowed("W"),
267        KeyCode::KeyX => Cow::Borrowed("X"),
268        KeyCode::KeyY => Cow::Borrowed("Y"),
269        KeyCode::KeyZ => Cow::Borrowed("Z"),
270
271        KeyCode::Minus => Cow::Borrowed("-"),
272        KeyCode::Equal => Cow::Borrowed("="),
273        KeyCode::BracketLeft => Cow::Borrowed("["),
274        KeyCode::BracketRight => Cow::Borrowed("]"),
275        KeyCode::Backslash => Cow::Borrowed("\\"),
276        KeyCode::Semicolon => Cow::Borrowed(";"),
277        KeyCode::Quote => Cow::Borrowed("'"),
278        KeyCode::Backquote => Cow::Borrowed("`"),
279        KeyCode::Comma => Cow::Borrowed(","),
280        KeyCode::Period => Cow::Borrowed("."),
281        KeyCode::Slash => Cow::Borrowed("/"),
282
283        KeyCode::F1 => Cow::Borrowed("F1"),
284        KeyCode::F2 => Cow::Borrowed("F2"),
285        KeyCode::F3 => Cow::Borrowed("F3"),
286        KeyCode::F4 => Cow::Borrowed("F4"),
287        KeyCode::F5 => Cow::Borrowed("F5"),
288        KeyCode::F6 => Cow::Borrowed("F6"),
289        KeyCode::F7 => Cow::Borrowed("F7"),
290        KeyCode::F8 => Cow::Borrowed("F8"),
291        KeyCode::F9 => Cow::Borrowed("F9"),
292        KeyCode::F10 => Cow::Borrowed("F10"),
293        KeyCode::F11 => Cow::Borrowed("F11"),
294        KeyCode::F12 => Cow::Borrowed("F12"),
295        KeyCode::F13 => Cow::Borrowed("F13"),
296        KeyCode::F14 => Cow::Borrowed("F14"),
297        KeyCode::F15 => Cow::Borrowed("F15"),
298        KeyCode::F16 => Cow::Borrowed("F16"),
299        KeyCode::F17 => Cow::Borrowed("F17"),
300        KeyCode::F18 => Cow::Borrowed("F18"),
301        KeyCode::F19 => Cow::Borrowed("F19"),
302        KeyCode::F20 => Cow::Borrowed("F20"),
303        KeyCode::F21 => Cow::Borrowed("F21"),
304        KeyCode::F22 => Cow::Borrowed("F22"),
305        KeyCode::F23 => Cow::Borrowed("F23"),
306        KeyCode::F24 => Cow::Borrowed("F24"),
307
308        KeyCode::Numpad0 => Cow::Borrowed("Num0"),
309        KeyCode::Numpad1 => Cow::Borrowed("Num1"),
310        KeyCode::Numpad2 => Cow::Borrowed("Num2"),
311        KeyCode::Numpad3 => Cow::Borrowed("Num3"),
312        KeyCode::Numpad4 => Cow::Borrowed("Num4"),
313        KeyCode::Numpad5 => Cow::Borrowed("Num5"),
314        KeyCode::Numpad6 => Cow::Borrowed("Num6"),
315        KeyCode::Numpad7 => Cow::Borrowed("Num7"),
316        KeyCode::Numpad8 => Cow::Borrowed("Num8"),
317        KeyCode::Numpad9 => Cow::Borrowed("Num9"),
318        KeyCode::NumpadAdd => Cow::Borrowed("Num+"),
319        KeyCode::NumpadSubtract => Cow::Borrowed("Num-"),
320        KeyCode::NumpadMultiply => Cow::Borrowed("Num*"),
321        KeyCode::NumpadDivide => Cow::Borrowed("Num/"),
322        KeyCode::NumpadDecimal => Cow::Borrowed("Num."),
323        KeyCode::NumpadEnter => Cow::Borrowed("NumEnter"),
324
325        other => Cow::Owned(other.to_string()),
326    }
327}
328
329#[cfg(test)]
330mod tests {
331    use super::*;
332
333    #[test]
334    fn format_chord_macos_orders_modifiers_and_formats_cmd() {
335        let chord = KeyChord::new(
336            KeyCode::KeyP,
337            Modifiers {
338                meta: true,
339                shift: true,
340                ..Default::default()
341            },
342        );
343        assert_eq!(format_chord(Platform::Macos, chord), "Cmd+Shift+P");
344    }
345
346    #[test]
347    fn format_chord_windows_orders_modifiers_and_formats_ctrl() {
348        let chord = KeyChord::new(
349            KeyCode::KeyP,
350            Modifiers {
351                ctrl: true,
352                shift: true,
353                ..Default::default()
354            },
355        );
356        assert_eq!(format_chord(Platform::Windows, chord), "Ctrl+Shift+P");
357    }
358
359    #[test]
360    fn format_chord_falls_back_to_code_token_for_unhandled_keys() {
361        let chord = KeyChord::new(KeyCode::PrintScreen, Modifiers::default());
362        assert_eq!(format_chord(Platform::Windows, chord), "PrintScreen");
363    }
364}