Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/platform/mac/event.rs
1use cocoa::foundation::NSUInteger;
2use std::{ffi::CStr, os::raw::c_char};
3 
4use strato_ui_core::event::{KeyEventDetails, ModifiersState};
5use strato_ui_core::platform::keyboard::{KeyCode, PhysicalKey};
6use strato_ui_core::{keymap::Keystroke, Event};
7 
8use cocoa::{
9 appkit::{NSEvent, NSEventModifierFlags, NSEventType},
10 base::{id, YES},
11 foundation::NSString,
12};
13use pathfinder_geometry::vector::vec2f;
14 
15use super::{
16 keycode::{scancode_to_physicalkey, Keycode},
17 utils::unicode_char_to_key,
18};
19 
20// Unpublished but widely known and stable flags for distinguishing left/right alt.
21// Google "NX_DEVICELALTKEYMASK" for more.
22const LEFT_ALT_MASK: NSUInteger = 0x00000020;
23const RIGHT_ALT_MASK: NSUInteger = 0x00000040;
24 
25fn modifier_flags_to_state(flags: NSEventModifierFlags) -> ModifiersState {
26 ModifiersState {
27 alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
28 cmd: flags.contains(NSEventModifierFlags::NSCommandKeyMask),
29 shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask),
30 ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask),
31 func: flags.contains(NSEventModifierFlags::NSFunctionKeyMask),
32 }
33}
34 
35fn native_key_code_to_key_code(native_key_code: u16) -> Option<KeyCode> {
36 let physical_key = scancode_to_physicalkey(native_key_code as u32);
37 match physical_key {
38 PhysicalKey::Code(key_code) => Some(key_code),
39 _ => None,
40 }
41}
42 
43/// # Safety
44/// This code is only unsafe since it requires interfacing with platform code.
45/// Creates an event from a native event, taking in the current window_height and whether this is
46/// the first mouse event on an inactive window that is causing the window to activate.
47pub unsafe fn from_native(
48 native_event: id,
49 window_height: Option<f32>,
50 is_first_mouse: bool,
51) -> Option<Event> {
52 let event_type = native_event.eventType();
53 
54 // Filter out event types that aren't in the NSEventType enum.
55 // See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
56 match event_type as u64 {
57 0 | 21 | 32 | 33 | 35 | 36 | 37 => {
58 return None;
59 }
60 _ => {}
61 }
62 let modifiers = modifier_flags_to_state(native_event.modifierFlags());
63 
64 match event_type {
65 NSEventType::NSKeyDown => {
66 let native_modifiers = native_event.modifierFlags();
67 
68 // Get the base character for this key without any modifiers (including Shift)
69 // using UCKeyTranslate via the platform's keyCodeToChar function.
70 // For example, Shift+1 on a US keyboard gives '!' as the key, but
71 // key_without_modifiers will be '1'.
72 let key_without_modifiers = Keycode(native_event.keyCode()).try_to_key_name(false);
73 
74 let details = KeyEventDetails {
75 left_alt: (native_modifiers.bits() & LEFT_ALT_MASK) != 0,
76 right_alt: (native_modifiers.bits() & RIGHT_ALT_MASK) != 0,
77 key_without_modifiers,
78 };
79 let unmodified_chars = native_event.charactersIgnoringModifiers();
80 let unmodified_chars = CStr::from_ptr(unmodified_chars.UTF8String() as *mut c_char)
81 .to_str()
82 .ok()?;
83 
84 let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
85 unicode_char_to_key(first_char as u16).unwrap_or(unmodified_chars)
86 } else {
87 return None;
88 };
89 
90 let keystroke = Keystroke {
91 ctrl: native_modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
92 alt: native_modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
93 shift: native_modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
94 cmd: native_modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
95 meta: false, /* handled separately */
96 key: unmodified_chars.into(),
97 };
98 
99 let chars = native_event.characters().UTF8String() as *mut c_char;
100 let chars = if chars.is_null() {
101 // `UTF8String` can return null in some rare cases where the
102 // string isn't valid UTF-8. For example, if the user
103 // enters a UTF-8 surrogate character, e.g. U+DDDD, via the
104 // Unicode Hex Input keyboard, the conversion will produce
105 // null.
106 String::new()
107 } else {
108 CStr::from_ptr(chars).to_str().ok()?.to_owned()
109 };
110 
111 Some(Event::KeyDown {
112 keystroke,
113 chars,
114 details,
115 is_composing: false,
116 })
117 }
118 NSEventType::NSMouseMoved => window_height.map(|window_height| Event::MouseMoved {
119 position: vec2f(
120 native_event.locationInWindow().x as f32,
121 window_height - native_event.locationInWindow().y as f32,
122 ),
123 cmd: native_event
124 .modifierFlags()
125 .contains(NSEventModifierFlags::NSCommandKeyMask),
126 shift: native_event
127 .modifierFlags()
128 .contains(NSEventModifierFlags::NSShiftKeyMask),
129 is_synthetic: false,
130 }),
131 NSEventType::NSFlagsChanged => {
132 let key_code = native_key_code_to_key_code(native_event.keyCode());
133 
134 window_height.map(|window_height| Event::ModifierStateChanged {
135 mouse_position: vec2f(
136 native_event.locationInWindow().x as f32,
137 window_height - native_event.locationInWindow().y as f32,
138 ),
139 modifiers,
140 key_code,
141 })
142 }
143 NSEventType::NSLeftMouseDown => window_height.map(|window_height| {
144 let position = vec2f(
145 native_event.locationInWindow().x as f32,
146 window_height - native_event.locationInWindow().y as f32,
147 );
148 let click_count = native_event.clickCount() as u32;
149 
150 // ctrl-click should actually be registered as a right-click
151 // https://support.apple.com/guide/mac-help/right-click-mh35853/mac
152 if modifiers.ctrl {
153 Event::RightMouseDown {
154 position,
155 cmd: modifiers.cmd,
156 shift: modifiers.shift,
157 click_count,
158 }
159 } else {
160 Event::LeftMouseDown {
161 position,
162 modifiers,
163 click_count,
164 is_first_mouse,
165 }
166 }
167 }),
168 NSEventType::NSLeftMouseUp => window_height.map(|window_height| Event::LeftMouseUp {
169 position: vec2f(
170 native_event.locationInWindow().x as f32,
171 window_height - native_event.locationInWindow().y as f32,
172 ),
173 modifiers,
174 }),
175 NSEventType::NSLeftMouseDragged => {
176 window_height.map(|window_height| Event::LeftMouseDragged {
177 position: vec2f(
178 native_event.locationInWindow().x as f32,
179 window_height - native_event.locationInWindow().y as f32,
180 ),
181 modifiers,
182 })
183 }
184 // TODO: This option is deprecated by Apple in favour of NSEventTypeOtherMouseDown
185 // but we'll likely need to update cocoa.
186 // See https://developer.apple.com/documentation/appkit/nsothermousedown.
187 NSEventType::NSOtherMouseDown => {
188 let window_height = window_height?;
189 let window_location = native_event.locationInWindow();
190 let position = vec2f(
191 window_location.x as f32,
192 window_height - (window_location.y as f32),
193 );
194 let modifier_flags = native_event.modifierFlags();
195 let cmd = modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask);
196 let shift = modifier_flags.contains(NSEventModifierFlags::NSShiftKeyMask);
197 let click_count = native_event.clickCount() as u32;
198 
199 match native_event.buttonNumber() {
200 2 => Some(Event::MiddleMouseDown {
201 position,
202 cmd,
203 shift,
204 click_count,
205 }),
206 3 => Some(Event::BackMouseDown {
207 position,
208 cmd,
209 shift,
210 click_count,
211 }),
212 4 => Some(Event::ForwardMouseDown {
213 position,
214 cmd,
215 shift,
216 click_count,
217 }),
218 _ => None,
219 }
220 }
221 // For trackpads, this event will get triggered by the user's secondary click setting.
222 NSEventType::NSRightMouseDown => window_height.map(|window_height| Event::RightMouseDown {
223 position: vec2f(
224 native_event.locationInWindow().x as f32,
225 window_height - native_event.locationInWindow().y as f32,
226 ),
227 cmd: native_event
228 .modifierFlags()
229 .contains(NSEventModifierFlags::NSCommandKeyMask),
230 shift: native_event
231 .modifierFlags()
232 .contains(NSEventModifierFlags::NSShiftKeyMask),
233 click_count: native_event.clickCount() as u32,
234 }),
235 NSEventType::NSScrollWheel => window_height.map(|window_height| Event::ScrollWheel {
236 position: vec2f(
237 native_event.locationInWindow().x as f32,
238 window_height - native_event.locationInWindow().y as f32,
239 ),
240 delta: vec2f(
241 native_event.scrollingDeltaX() as f32,
242 native_event.scrollingDeltaY() as f32,
243 ),
244 precise: native_event.hasPreciseScrollingDeltas() == YES,
245 modifiers,
246 }),
247 _ => None,
248 }
249}
250