Partially replace custom AppKit bindings with icrate's autogenerated bindings (#2982)

* Refactor winit-specific cursor logic out of appkit module

* Add relevant AppKit features that we depend on

* Use icrate's NSImageRep and NSBitmapImageRep

* Use icrate's NSImage

* Use icrate's NSCursor

* Use icrate's NSAppearance

* Use icrate's NSScreen

* Use icrate's NSButton

* Use icrate's NSAppKitVersionNumber

* Use icrate's NSTextInputContext

* Use icrate's NSColor

* Use icrate's NSEvent

* Use icrate's NSMenu and NSMenuItem

* Use icrate's NSPasteboard

* Use icrate's NSResponder

* Use icrate's NSTextInputClient

* Use icrate's NSView
This commit is contained in:
Mads Marquart 2023-12-23 20:58:38 +01:00 committed by GitHub
parent 7d5bee767c
commit 674657efb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 547 additions and 1362 deletions

View file

@ -1,10 +1,17 @@
#![allow(clippy::unnecessary_cast)]
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::ptr;
use icrate::AppKit::{
NSCursor, NSEvent, NSEventPhaseBegan, NSEventPhaseCancelled, NSEventPhaseChanged,
NSEventPhaseEnded, NSEventPhaseMayBegin, NSResponder, NSTextInputClient, NSTrackingRectTag,
NSView,
};
use icrate::Foundation::{
NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString,
NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize, NSString, NSUInteger,
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
NSMutableAttributedString, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, NSSize,
NSString, NSUInteger,
};
use objc2::rc::{Id, WeakId};
use objc2::runtime::{AnyObject, Sel};
@ -12,11 +19,10 @@ use objc2::{
class, declare_class, msg_send, msg_send_id, mutability, sel, ClassType, DeclaredClass,
};
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{lalt_pressed, ralt_pressed};
use super::{
appkit::{
NSApp, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag,
NSView,
},
appkit::NSApp,
event::{code_to_key, code_to_location},
};
use crate::{
@ -48,7 +54,7 @@ impl Default for CursorState {
fn default() -> Self {
Self {
visible: true,
cursor: Default::default(),
cursor: default_cursor(),
}
}
}
@ -150,7 +156,7 @@ declare_class!(
unsafe impl ClassType for WinitView {
#[inherits(NSResponder, NSObject)]
type Super = NSView;
type Mutability = mutability::InteriorMutable;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitView";
}
@ -167,7 +173,8 @@ declare_class!(
}
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
}
@ -179,7 +186,8 @@ declare_class!(
}
let rect = self.frame();
let tracking_rect = self.add_tracking_rect(rect, false);
let tracking_rect = unsafe { self.addTrackingRect_owner_userData_assumeInside(rect, self, ptr::null_mut(), false) };
assert_ne!(tracking_rect, 0, "failed adding tracking rect");
self.ivars().tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because:
@ -227,9 +235,9 @@ declare_class!(
let cursor_state = self.ivars().cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects`
if cursor_state.visible {
self.addCursorRect(bounds, &cursor_state.cursor);
self.addCursorRect_cursor(bounds, &cursor_state.cursor);
} else {
self.addCursorRect(bounds, &NSCursor::invisible());
self.addCursorRect_cursor(bounds, &invisible_cursor());
}
}
}
@ -295,7 +303,7 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Enabled));
}
if self.hasMarkedText() {
if unsafe { self.hasMarkedText() } {
self.ivars().ime_state.set(ImeState::Preedit);
} else {
// In case the preedit was cleared, set IME into the Ground state.
@ -388,7 +396,7 @@ declare_class!(
let is_control = string.chars().next().map_or(false, |c| c.is_control());
// Commit only if we have marked text.
if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
if unsafe { self.hasMarkedText() } && self.is_ime_enabled() && !is_control {
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.ivars().ime_state.set(ImeState::Commited);
@ -409,7 +417,7 @@ declare_class!(
self.ivars().forward_key_to_app.set(true);
if self.hasMarkedText() && self.ivars().ime_state.get() == ImeState::Preedit {
if unsafe { self.hasMarkedText() } && self.ivars().ime_state.get() == ImeState::Preedit {
// Leave preedit so that we also report the key-up for this key.
self.ivars().ime_state.set(ImeState::Ground);
}
@ -467,7 +475,7 @@ declare_class!(
};
if !had_ime_input || self.ivars().forward_key_to_app.get() {
let key_event = create_key_event(&event, true, event.is_a_repeat(), None);
let key_event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
event: key_event,
@ -536,7 +544,7 @@ declare_class!(
.expect("could not find current event");
self.update_modifiers(&event, false);
let event = create_key_event(&event, true, event.is_a_repeat(), None);
let event = create_key_event(&event, true, unsafe { event.isARepeat() }, None);
self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID,
@ -633,8 +641,8 @@ declare_class!(
self.mouse_motion(event);
let delta = {
let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY());
if event.hasPreciseScrollingDeltas() {
let (x, y) = unsafe { (event.scrollingDeltaX(), event.scrollingDeltaY()) };
if unsafe { event.hasPreciseScrollingDeltas() } {
let delta = LogicalPosition::new(x, y).to_physical(self.scale_factor());
MouseScrollDelta::PixelDelta(delta)
} else {
@ -646,18 +654,19 @@ declare_class!(
// be mutually exclusive anyhow, which is why the API is rather incoherent). If no momentum
// phase is recorded (or rather, the started/ended cases of the momentum phase) then we
// report the touch phase.
let phase = match event.momentumPhase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.momentumPhase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => {
TouchPhase::Started
}
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => match event.phase() {
NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
_ => match unsafe { event.phase() } {
NSEventPhaseMayBegin | NSEventPhaseBegan => {
TouchPhase::Started
}
NSEventPhase::NSEventPhaseEnded | NSEventPhase::NSEventPhaseCancelled => {
NSEventPhaseEnded | NSEventPhaseCancelled => {
TouchPhase::Ended
}
_ => TouchPhase::Moved,
@ -678,17 +687,18 @@ declare_class!(
fn magnify_with_event(&self, event: &NSEvent) {
trace_scope!("magnifyWithEvent:");
let phase = match event.phase() {
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::TouchpadMagnify {
device_id: DEVICE_ID,
delta: event.magnification(),
delta: unsafe { event.magnification() },
phase,
});
}
@ -706,17 +716,18 @@ declare_class!(
fn rotate_with_event(&self, event: &NSEvent) {
trace_scope!("rotateWithEvent:");
let phase = match event.phase() {
NSEventPhase::NSEventPhaseBegan => TouchPhase::Started,
NSEventPhase::NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhase::NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
#[allow(non_upper_case_globals)]
let phase = match unsafe { event.phase() } {
NSEventPhaseBegan => TouchPhase::Started,
NSEventPhaseChanged => TouchPhase::Moved,
NSEventPhaseCancelled => TouchPhase::Cancelled,
NSEventPhaseEnded => TouchPhase::Ended,
_ => return,
};
self.queue_event(WindowEvent::TouchpadRotate {
device_id: DEVICE_ID,
delta: event.rotation(),
delta: unsafe { event.rotation() },
phase,
});
}
@ -729,8 +740,8 @@ declare_class!(
self.queue_event(WindowEvent::TouchpadPressure {
device_id: DEVICE_ID,
pressure: event.pressure(),
stage: event.stage() as i64,
pressure: unsafe { event.pressure() },
stage: unsafe { event.stage() } as i64,
});
}
@ -753,7 +764,8 @@ declare_class!(
impl WinitView {
pub(super) fn new(window: &WinitWindow, accepts_first_mouse: bool) -> Id<Self> {
let this = Self::alloc().set_ivars(ViewState {
let mtm = MainThreadMarker::from(window);
let this = mtm.alloc().set_ivars(ViewState {
accepts_first_mouse,
_ns_window: WeakId::new(&window.retain()),
..Default::default()
@ -902,8 +914,8 @@ impl WinitView {
// later will work though, since the flags are attached to the event and contain valid
// information.
'send_event: {
if is_flags_changed_event && ns_event.key_code() != 0 {
let scancode = ns_event.key_code();
if is_flags_changed_event && unsafe { ns_event.keyCode() } != 0 {
let scancode = unsafe { ns_event.keyCode() };
let physical_key = PhysicalKey::from_scancode(scancode as u32);
// We'll correct the `is_press` later.
@ -1014,7 +1026,7 @@ impl WinitView {
}
fn mouse_motion(&self, event: &NSEvent) {
let window_point = event.locationInWindow();
let window_point = unsafe { event.locationInWindow() };
let view_point = self.convertPoint_fromView(window_point, None);
let view_rect = self.frame();
@ -1023,7 +1035,7 @@ impl WinitView {
|| view_point.x > view_rect.size.width
|| view_point.y > view_rect.size.height
{
let mouse_buttons_down = NSEvent::pressedMouseButtons();
let mouse_buttons_down = unsafe { NSEvent::pressedMouseButtons() };
if mouse_buttons_down == 0 {
// Point is outside of the client area (view) and no buttons are pressed
return;
@ -1050,7 +1062,7 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
// For the other events, it's always set to 0.
// MacOS only defines the left, right and middle buttons, 3..=31 are left as generic buttons,
// but 3 and 4 are very commonly used as Back and Forward by hardware vendors and applications.
match event.buttonNumber() {
match unsafe { event.buttonNumber() } {
0 => MouseButton::Left,
1 => MouseButton::Right,
2 => MouseButton::Middle,
@ -1066,30 +1078,35 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
fn replace_event(event: &NSEvent, option_as_alt: OptionAsAlt) -> Id<NSEvent> {
let ev_mods = event_mods(event).state;
let ignore_alt_characters = match option_as_alt {
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
OptionAsAlt::OnlyLeft if lalt_pressed(event) => true,
OptionAsAlt::OnlyRight if ralt_pressed(event) => true,
OptionAsAlt::Both if ev_mods.alt_key() => true,
_ => false,
} && !ev_mods.control_key()
&& !ev_mods.super_key();
if ignore_alt_characters {
let ns_chars = event
.charactersIgnoringModifiers()
.expect("expected characters to be non-null");
let ns_chars = unsafe {
event
.charactersIgnoringModifiers()
.expect("expected characters to be non-null")
};
NSEvent::keyEventWithType(
event.type_(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.window_number(),
None,
&ns_chars,
&ns_chars,
event.is_a_repeat(),
event.key_code(),
)
unsafe {
NSEvent::keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode(
event.r#type(),
event.locationInWindow(),
event.modifierFlags(),
event.timestamp(),
event.windowNumber(),
None,
&ns_chars,
&ns_chars,
event.isARepeat(),
event.keyCode(),
)
.unwrap()
}
} else {
event.copy()
}