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 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}