Stop using &mut in Objective-C delegate methods (#2925)

* Make iOS declared classes not use &mut

* Prepare `init` methods for not having access to &mut self

* Prepare WinitWindow methods for not having access to &mut self

* Convert a bit of WinitView's to use interior mutability

* Convert a bit more of WinitView's to use interior mutability

* Convert the rest of WinitView to use interior mutability

* Use interior mutability instead of a Mutex for the CursorState

* Use interior mutability in WinitWindowDelegate
This commit is contained in:
Mads Marquart 2023-07-08 22:36:42 +03:00 committed by GitHub
parent 4652d48105
commit bca57ed0b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 426 additions and 377 deletions

View file

@ -70,6 +70,10 @@ impl From<UIUserInterfaceIdiom> for Idiom {
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger); pub struct UIRectEdge(NSUInteger);
impl UIRectEdge {
pub(crate) const NONE: Self = Self(0);
}
unsafe impl Encode for UIRectEdge { unsafe impl Encode for UIRectEdge {
const ENCODING: Encoding = NSUInteger::ENCODING; const ENCODING: Encoding = NSUInteger::ENCODING;
} }

View file

@ -1,5 +1,8 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr::NonNull;
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet}; use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::rc::{Id, Shared}; use objc2::rc::{Id, Shared};
use objc2::runtime::Class; use objc2::runtime::Class;
@ -260,12 +263,16 @@ impl WinitView {
} }
} }
struct ViewControllerState {
prefers_status_bar_hidden: Cell<bool>,
prefers_home_indicator_auto_hidden: Cell<bool>,
supported_orientations: Cell<UIInterfaceOrientationMask>,
preferred_screen_edges_deferring_system_gestures: Cell<UIRectEdge>,
}
declare_class!( declare_class!(
pub(crate) struct WinitViewController { pub(crate) struct WinitViewController {
_prefers_status_bar_hidden: bool, state: IvarDrop<Box<ViewControllerState>>,
_prefers_home_indicator_auto_hidden: bool,
_supported_orientations: UIInterfaceOrientationMask,
_preferred_screen_edges_deferring_system_gestures: UIRectEdge,
} }
unsafe impl ClassType for WinitViewController { unsafe impl ClassType for WinitViewController {
@ -274,88 +281,87 @@ declare_class!(
const NAME: &'static str = "WinitUIViewController"; const NAME: &'static str = "WinitUIViewController";
} }
unsafe impl WinitViewController {
#[sel(init)]
fn init(&mut self) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
// These are set in WinitViewController::new, it's just to set them
// to _something_.
Ivar::write(
&mut this.state,
Box::new(ViewControllerState {
prefers_status_bar_hidden: Cell::new(false),
prefers_home_indicator_auto_hidden: Cell::new(false),
supported_orientations: Cell::new(UIInterfaceOrientationMask::All),
preferred_screen_edges_deferring_system_gestures: Cell::new(
UIRectEdge::NONE,
),
}),
);
NonNull::from(this)
})
}
}
unsafe impl WinitViewController { unsafe impl WinitViewController {
#[sel(shouldAutorotate)] #[sel(shouldAutorotate)]
fn should_autorotate(&self) -> bool { fn should_autorotate(&self) -> bool {
true true
} }
}
unsafe impl WinitViewController {
#[sel(prefersStatusBarHidden)] #[sel(prefersStatusBarHidden)]
fn prefers_status_bar_hidden(&self) -> bool { fn prefers_status_bar_hidden(&self) -> bool {
*self._prefers_status_bar_hidden self.state.prefers_status_bar_hidden.get()
}
#[sel(setPrefersStatusBarHidden:)]
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
*self._prefers_status_bar_hidden = val;
self.setNeedsStatusBarAppearanceUpdate();
} }
#[sel(prefersHomeIndicatorAutoHidden)] #[sel(prefersHomeIndicatorAutoHidden)]
fn prefers_home_indicator_auto_hidden(&self) -> bool { fn prefers_home_indicator_auto_hidden(&self) -> bool {
*self._prefers_home_indicator_auto_hidden self.state.prefers_home_indicator_auto_hidden.get()
}
#[sel(setPrefersHomeIndicatorAutoHidden:)]
fn set_prefers_home_indicator_auto_hidden(&mut self, val: bool) {
*self._prefers_home_indicator_auto_hidden = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
} }
#[sel(supportedInterfaceOrientations)] #[sel(supportedInterfaceOrientations)]
fn supported_orientations(&self) -> UIInterfaceOrientationMask { fn supported_orientations(&self) -> UIInterfaceOrientationMask {
*self._supported_orientations self.state.supported_orientations.get()
}
#[sel(setSupportedInterfaceOrientations:)]
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
*self._supported_orientations = val;
UIViewController::attemptRotationToDeviceOrientation();
} }
#[sel(preferredScreenEdgesDeferringSystemGestures)] #[sel(preferredScreenEdgesDeferringSystemGestures)]
fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge { fn preferred_screen_edges_deferring_system_gestures(&self) -> UIRectEdge {
*self._preferred_screen_edges_deferring_system_gestures self.state
.preferred_screen_edges_deferring_system_gestures
.get()
} }
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
fn set_preferred_screen_edges_deferring_system_gestures(&mut self, val: UIRectEdge) {
*self._preferred_screen_edges_deferring_system_gestures = val;
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
}
);
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitViewController {
#[sel(setPrefersStatusBarHidden:)]
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
#[sel(setSupportedInterfaceOrientations:)]
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
#[sel(setPrefersHomeIndicatorAutoHidden:)]
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
} }
); );
impl WinitViewController { impl WinitViewController {
pub(crate) fn set_prefers_status_bar_hidden(&self, val: bool) {
self.state.prefers_status_bar_hidden.set(val);
self.setNeedsStatusBarAppearanceUpdate();
}
pub(crate) fn set_prefers_home_indicator_auto_hidden(&self, val: bool) {
self.state.prefers_home_indicator_auto_hidden.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden {
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
} else {
os_capabilities.home_indicator_hidden_err_msg("ignoring")
}
}
pub(crate) fn set_preferred_screen_edges_deferring_system_gestures(&self, val: UIRectEdge) {
self.state
.preferred_screen_edges_deferring_system_gestures
.set(val);
let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures {
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
} else {
os_capabilities.defer_system_gestures_err_msg("ignoring")
}
}
pub(crate) fn set_supported_interface_orientations( pub(crate) fn set_supported_interface_orientations(
&self, &self,
mtm: MainThreadMarker, mtm: MainThreadMarker,
@ -378,7 +384,8 @@ impl WinitViewController {
| UIInterfaceOrientationMask::PortraitUpsideDown | UIInterfaceOrientationMask::PortraitUpsideDown
} }
}; };
self.setSupportedInterfaceOrientations(mask); self.state.supported_orientations.set(mask);
UIViewController::attemptRotationToDeviceOrientation();
} }
pub(crate) fn new( pub(crate) fn new(
@ -390,13 +397,15 @@ impl WinitViewController {
let this: Id<Self, Shared> = let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] }; unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden); this.set_prefers_status_bar_hidden(platform_attributes.prefers_status_bar_hidden);
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations); this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden); this.set_prefers_home_indicator_auto_hidden(
platform_attributes.prefers_home_indicator_hidden,
);
this.setPreferredScreenEdgesDeferringSystemGestures( this.set_preferred_screen_edges_deferring_system_gestures(
platform_attributes platform_attributes
.preferred_screen_edges_deferring_system_gestures .preferred_screen_edges_deferring_system_gestures
.into(), .into(),

View file

@ -23,7 +23,6 @@ use crate::{
platform_impl::platform::{ platform_impl::platform::{
app_state, app_state,
event_loop::{EventProxy, EventWrapper}, event_loop::{EventProxy, EventWrapper},
ffi::UIRectEdge,
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
@ -538,17 +537,16 @@ impl Inner {
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.view_controller self.view_controller
.setPrefersHomeIndicatorAutoHidden(hidden); .set_prefers_home_indicator_auto_hidden(hidden);
} }
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
self.view_controller self.view_controller
.setPreferredScreenEdgesDeferringSystemGestures(edges); .set_preferred_screen_edges_deferring_system_gestures(edges.into());
} }
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
self.view_controller.setPrefersStatusBarHidden(hidden); self.view_controller.set_prefers_status_bar_hidden(hidden);
} }
} }

View file

@ -1,3 +1,5 @@
use std::ptr::NonNull;
use objc2::foundation::NSObject; use objc2::foundation::NSObject;
use objc2::rc::{Id, Shared}; use objc2::rc::{Id, Shared};
use objc2::runtime::Object; use objc2::runtime::Object;
@ -21,18 +23,18 @@ declare_class!(
unsafe impl ApplicationDelegate { unsafe impl ApplicationDelegate {
#[sel(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)] #[sel(initWithActivationPolicy:defaultMenu:activateIgnoringOtherApps:)]
fn init( unsafe fn init(
&mut self, &mut self,
activation_policy: NSApplicationActivationPolicy, activation_policy: NSApplicationActivationPolicy,
default_menu: bool, default_menu: bool,
activate_ignoring_other_apps: bool, activate_ignoring_other_apps: bool,
) -> Option<&mut Self> { ) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| { this.map(|this| {
*this.activation_policy = activation_policy; *this.activation_policy = activation_policy;
*this.default_menu = default_menu; *this.default_menu = default_menu;
*this.activate_ignoring_other_apps = activate_ignoring_other_apps; *this.activate_ignoring_other_apps = activate_ignoring_other_apps;
this NonNull::from(this)
}) })
} }

View file

@ -17,8 +17,7 @@ extern_class!(
extern_methods!( extern_methods!(
unsafe impl NSResponder { unsafe impl NSResponder {
// TODO: Allow "immutably" on main thread
#[sel(interpretKeyEvents:)] #[sel(interpretKeyEvents:)]
pub unsafe fn interpretKeyEvents(&mut self, events: &NSArray<NSEvent, Shared>); pub unsafe fn interpretKeyEvents(&self, events: &NSArray<NSEvent, Shared>);
} }
); );

View file

@ -66,7 +66,7 @@ extern_methods!(
pub fn setWantsLayer(&self, wants_layer: bool); pub fn setWantsLayer(&self, wants_layer: bool);
#[sel(setPostsFrameChangedNotifications:)] #[sel(setPostsFrameChangedNotifications:)]
pub fn setPostsFrameChangedNotifications(&mut self, value: bool); pub fn setPostsFrameChangedNotifications(&self, value: bool);
#[sel(removeTrackingRect:)] #[sel(removeTrackingRect:)]
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag); pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);

View file

@ -2,7 +2,7 @@ use std::ops::Deref;
use dispatch::Queue; use dispatch::Queue;
use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString}; use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString};
use objc2::rc::{autoreleasepool, Id}; use objc2::rc::autoreleasepool;
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
@ -209,8 +209,7 @@ pub(crate) fn set_ime_cursor_area_sync(
) { ) {
let window = MainThreadSafe(window); let window = MainThreadSafe(window);
run_on_main(move || { run_on_main(move || {
// TODO(madsmtm): Remove the need for this window.view().set_ime_cursor_area(logical_spot, size);
unsafe { Id::from_shared(window.view()) }.set_ime_cursor_area(logical_spot, size);
}); });
} }

View file

@ -1,12 +1,11 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::{ use std::boxed::Box;
boxed::Box, use std::cell::{Cell, RefCell};
collections::{HashMap, VecDeque}, use std::collections::{HashMap, VecDeque};
os::raw::*, use std::os::raw::*;
ptr, str, use std::ptr::{self, NonNull};
sync::Mutex, use std::str;
};
use objc2::declare::{Ivar, IvarDrop}; use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{ use objc2::foundation::{
@ -41,9 +40,9 @@ use crate::{
}; };
#[derive(Debug)] #[derive(Debug)]
pub struct CursorState { struct CursorState {
pub visible: bool, visible: bool,
pub(super) cursor: Id<NSCursor, Shared>, cursor: Id<NSCursor, Shared>,
} }
impl Default for CursorState { impl Default for CursorState {
@ -55,8 +54,9 @@ impl Default for CursorState {
} }
} }
#[derive(Debug, Eq, PartialEq, Clone, Copy)] #[derive(Debug, Eq, PartialEq, Clone, Copy, Default)]
enum ImeState { enum ImeState {
#[default]
/// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user. /// The IME events are disabled, so only `ReceivedCharacter` is being sent to the user.
Disabled, Disabled,
@ -88,7 +88,7 @@ impl ModLocationMask {
} }
} }
pub fn key_to_modifier(key: &Key) -> ModifiersState { fn key_to_modifier(key: &Key) -> ModifiersState {
match key { match key {
Key::Alt => ModifiersState::ALT, Key::Alt => ModifiersState::ALT,
Key::Control => ModifiersState::CONTROL, Key::Control => ModifiersState::CONTROL,
@ -118,26 +118,28 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
} }
} }
#[derive(Debug)] #[derive(Debug, Default)]
pub(super) struct ViewState { struct ViewState {
pub cursor_state: Mutex<CursorState>, cursor_state: RefCell<CursorState>,
ime_position: LogicalPosition<f64>, ime_position: Cell<LogicalPosition<f64>>,
ime_size: LogicalSize<f64>, ime_size: Cell<LogicalSize<f64>>,
pub(super) modifiers: Modifiers, modifiers: Cell<Modifiers>,
phys_modifiers: HashMap<Key, ModLocationMask>, phys_modifiers: RefCell<HashMap<Key, ModLocationMask>>,
tracking_rect: Option<NSTrackingRectTag>, tracking_rect: Cell<Option<NSTrackingRectTag>>,
// phys_modifiers: HashSet<KeyCode>, ime_state: Cell<ImeState>,
ime_state: ImeState, input_source: RefCell<String>,
input_source: String,
/// True iff the application wants IME events. /// True iff the application wants IME events.
/// ///
/// Can be set using `set_ime_allowed` /// Can be set using `set_ime_allowed`
ime_allowed: bool, ime_allowed: Cell<bool>,
/// True if the current key event should be forwarded /// True if the current key event should be forwarded
/// to the application, even during IME /// to the application, even during IME
forward_key_to_app: bool, forward_key_to_app: Cell<bool>,
marked_text: RefCell<Id<NSMutableAttributedString, Owned>>,
accepts_first_mouse: bool,
} }
declare_class!( declare_class!(
@ -146,9 +148,7 @@ declare_class!(
pub(super) struct WinitView { pub(super) struct WinitView {
// Weak reference because the window keeps a strong reference to the view // Weak reference because the window keeps a strong reference to the view
_ns_window: IvarDrop<Box<WeakId<WinitWindow>>>, _ns_window: IvarDrop<Box<WeakId<WinitWindow>>>,
pub(super) state: IvarDrop<Box<ViewState>>, state: IvarDrop<Box<ViewState>>,
marked_text: IvarDrop<Id<NSMutableAttributedString, Owned>>,
accepts_first_mouse: bool,
} }
unsafe impl ClassType for WinitView { unsafe impl ClassType for WinitView {
@ -158,24 +158,16 @@ declare_class!(
unsafe impl WinitView { unsafe impl WinitView {
#[sel(initWithId:acceptsFirstMouse:)] #[sel(initWithId:acceptsFirstMouse:)]
fn init_with_id( unsafe fn init_with_id(
&mut self, &mut self,
window: &WinitWindow, window: &WinitWindow,
accepts_first_mouse: bool, accepts_first_mouse: bool,
) -> Option<&mut Self> { ) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] }; let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| { this.map(|this| {
let state = ViewState { let state = ViewState {
cursor_state: Default::default(), accepts_first_mouse,
ime_position: LogicalPosition::new(0.0, 0.0), ..Default::default()
ime_size: Default::default(),
modifiers: Default::default(),
phys_modifiers: Default::default(),
tracking_rect: None,
ime_state: ImeState::Disabled,
input_source: String::new(),
ime_allowed: false,
forward_key_to_app: false,
}; };
Ivar::write( Ivar::write(
@ -183,8 +175,6 @@ declare_class!(
Box::new(WeakId::new(&window.retain())), Box::new(WeakId::new(&window.retain())),
); );
Ivar::write(&mut this.state, Box::new(state)); Ivar::write(&mut this.state, Box::new(state));
Ivar::write(&mut this.marked_text, NSMutableAttributedString::new());
Ivar::write(&mut this.accepts_first_mouse, accepts_first_mouse);
this.setPostsFrameChangedNotifications(true); this.setPostsFrameChangedNotifications(true);
@ -204,15 +194,15 @@ declare_class!(
]; ];
} }
this.state.input_source = this.current_input_source(); *this.state.input_source.borrow_mut() = this.current_input_source();
this NonNull::from(this)
}) })
} }
} }
unsafe impl WinitView { unsafe impl WinitView {
#[sel(viewDidMoveToWindow)] #[sel(viewDidMoveToWindow)]
fn view_did_move_to_window(&mut self) { fn view_did_move_to_window(&self) {
trace_scope!("viewDidMoveToWindow"); trace_scope!("viewDidMoveToWindow");
if let Some(tracking_rect) = self.state.tracking_rect.take() { if let Some(tracking_rect) = self.state.tracking_rect.take() {
self.removeTrackingRect(tracking_rect); self.removeTrackingRect(tracking_rect);
@ -220,11 +210,11 @@ declare_class!(
let rect = self.visibleRect(); let rect = self.visibleRect();
let tracking_rect = self.add_tracking_rect(rect, false); let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect = Some(tracking_rect); self.state.tracking_rect.set(Some(tracking_rect));
} }
#[sel(frameDidChange:)] #[sel(frameDidChange:)]
fn frame_did_change(&mut self, _event: &NSEvent) { fn frame_did_change(&self, _event: &NSEvent) {
trace_scope!("frameDidChange:"); trace_scope!("frameDidChange:");
if let Some(tracking_rect) = self.state.tracking_rect.take() { if let Some(tracking_rect) = self.state.tracking_rect.take() {
self.removeTrackingRect(tracking_rect); self.removeTrackingRect(tracking_rect);
@ -232,7 +222,7 @@ declare_class!(
let rect = self.visibleRect(); let rect = self.visibleRect();
let tracking_rect = self.add_tracking_rect(rect, false); let tracking_rect = self.add_tracking_rect(rect, false);
self.state.tracking_rect = Some(tracking_rect); self.state.tracking_rect.set(Some(tracking_rect));
// Emit resize event here rather than from windowDidResize because: // Emit resize event here rather than from windowDidResize because:
// 1. When a new window is created as a tab, the frame size may change without a window resize occurring. // 1. When a new window is created as a tab, the frame size may change without a window resize occurring.
@ -243,7 +233,7 @@ declare_class!(
} }
#[sel(drawRect:)] #[sel(drawRect:)]
fn draw_rect(&mut self, rect: NSRect) { fn draw_rect(&self, rect: NSRect) {
trace_scope!("drawRect:"); trace_scope!("drawRect:");
// It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`. // It's a workaround for https://github.com/rust-windowing/winit/issues/2640, don't replace with `self.window_id()`.
@ -276,7 +266,7 @@ declare_class!(
fn reset_cursor_rects(&self) { fn reset_cursor_rects(&self) {
trace_scope!("resetCursorRects"); trace_scope!("resetCursorRects");
let bounds = self.bounds(); let bounds = self.bounds();
let cursor_state = self.state.cursor_state.lock().unwrap(); let cursor_state = self.state.cursor_state.borrow();
// We correctly invoke `addCursorRect` only from inside `resetCursorRects` // We correctly invoke `addCursorRect` only from inside `resetCursorRects`
if cursor_state.visible { if cursor_state.visible {
self.addCursorRect(bounds, &cursor_state.cursor); self.addCursorRect(bounds, &cursor_state.cursor);
@ -290,13 +280,13 @@ declare_class!(
#[sel(hasMarkedText)] #[sel(hasMarkedText)]
fn has_marked_text(&self) -> bool { fn has_marked_text(&self) -> bool {
trace_scope!("hasMarkedText"); trace_scope!("hasMarkedText");
self.marked_text.len_utf16() > 0 self.state.marked_text.borrow().len_utf16() > 0
} }
#[sel(markedRange)] #[sel(markedRange)]
fn marked_range(&self) -> NSRange { fn marked_range(&self) -> NSRange {
trace_scope!("markedRange"); trace_scope!("markedRange");
let length = self.marked_text.len_utf16(); let length = self.state.marked_text.borrow().len_utf16();
if length > 0 { if length > 0 {
NSRange::new(0, length) NSRange::new(0, length)
} else { } else {
@ -312,7 +302,7 @@ declare_class!(
#[sel(setMarkedText:selectedRange:replacementRange:)] #[sel(setMarkedText:selectedRange:replacementRange:)]
fn set_marked_text( fn set_marked_text(
&mut self, &self,
string: &NSObject, string: &NSObject,
_selected_range: NSRange, _selected_range: NSRange,
_replacement_range: NSRange, _replacement_range: NSRange,
@ -339,19 +329,19 @@ declare_class!(
}; };
// Update marked text. // Update marked text.
*self.marked_text = marked_text; *self.state.marked_text.borrow_mut() = marked_text;
// Notify IME is active if application still doesn't know it. // Notify IME is active if application still doesn't know it.
if self.state.ime_state == ImeState::Disabled { if self.state.ime_state.get() == ImeState::Disabled {
self.state.input_source = self.current_input_source(); *self.state.input_source.borrow_mut() = self.current_input_source();
self.queue_event(WindowEvent::Ime(Ime::Enabled)); self.queue_event(WindowEvent::Ime(Ime::Enabled));
} }
if self.hasMarkedText() { if self.hasMarkedText() {
self.state.ime_state = ImeState::Preedit; self.state.ime_state.set(ImeState::Preedit);
} else { } else {
// In case the preedit was cleared, set IME into the Ground state. // In case the preedit was cleared, set IME into the Ground state.
self.state.ime_state = ImeState::Ground; self.state.ime_state.set(ImeState::Ground);
} }
// Empty string basically means that there's no preedit, so indicate that by sending // Empty string basically means that there's no preedit, so indicate that by sending
@ -367,9 +357,9 @@ declare_class!(
} }
#[sel(unmarkText)] #[sel(unmarkText)]
fn unmark_text(&mut self) { fn unmark_text(&self) {
trace_scope!("unmarkText"); trace_scope!("unmarkText");
*self.marked_text = NSMutableAttributedString::new(); *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
let input_context = self.inputContext().expect("input context"); let input_context = self.inputContext().expect("input context");
input_context.discardMarkedText(); input_context.discardMarkedText();
@ -377,7 +367,7 @@ declare_class!(
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
if self.is_ime_enabled() { if self.is_ime_enabled() {
// Leave the Preedit self.state // Leave the Preedit self.state
self.state.ime_state = ImeState::Ground; self.state.ime_state.set(ImeState::Ground);
} else { } else {
warn!("Expected to have IME enabled when receiving unmarkText"); warn!("Expected to have IME enabled when receiving unmarkText");
} }
@ -416,14 +406,14 @@ declare_class!(
let content_rect = window.contentRectForFrameRect(window.frame()); let content_rect = window.contentRectForFrameRect(window.frame());
let base_x = content_rect.origin.x as f64; let base_x = content_rect.origin.x as f64;
let base_y = (content_rect.origin.y + content_rect.size.height) as f64; let base_y = (content_rect.origin.y + content_rect.size.height) as f64;
let x = base_x + self.state.ime_position.x; let x = base_x + self.state.ime_position.get().x;
let y = base_y - self.state.ime_position.y; let y = base_y - self.state.ime_position.get().y;
let LogicalSize { width, height } = self.state.ime_size; let LogicalSize { width, height } = self.state.ime_size.get();
NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height)) NSRect::new(NSPoint::new(x as _, y as _), NSSize::new(width, height))
} }
#[sel(insertText:replacementRange:)] #[sel(insertText:replacementRange:)]
fn insert_text(&mut self, string: &NSObject, _replacement_range: NSRange) { fn insert_text(&self, string: &NSObject, _replacement_range: NSRange) {
trace_scope!("insertText:replacementRange:"); trace_scope!("insertText:replacementRange:");
// SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`. // SAFETY: This method is guaranteed to get either a `NSString` or a `NSAttributedString`.
@ -443,45 +433,49 @@ declare_class!(
if self.hasMarkedText() && self.is_ime_enabled() && !is_control { if self.hasMarkedText() && self.is_ime_enabled() && !is_control {
self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None))); self.queue_event(WindowEvent::Ime(Ime::Preedit(String::new(), None)));
self.queue_event(WindowEvent::Ime(Ime::Commit(string))); self.queue_event(WindowEvent::Ime(Ime::Commit(string)));
self.state.ime_state = ImeState::Commited; self.state.ime_state.set(ImeState::Commited);
} }
} }
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
// readable" character happens, i.e. newlines, tabs, and Ctrl+C. // readable" character happens, i.e. newlines, tabs, and Ctrl+C.
#[sel(doCommandBySelector:)] #[sel(doCommandBySelector:)]
fn do_command_by_selector(&mut self, _command: Sel) { fn do_command_by_selector(&self, _command: Sel) {
trace_scope!("doCommandBySelector:"); trace_scope!("doCommandBySelector:");
// We shouldn't forward any character from just commited text, since we'll end up sending // We shouldn't forward any character from just commited text, since we'll end up sending
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case, // it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
// which is not desired given it was used to confirm IME input. // which is not desired given it was used to confirm IME input.
if self.state.ime_state == ImeState::Commited { if self.state.ime_state.get() == ImeState::Commited {
return; return;
} }
self.state.forward_key_to_app = true; self.state.forward_key_to_app.set(true);
if self.hasMarkedText() && self.state.ime_state == ImeState::Preedit { if self.hasMarkedText() && self.state.ime_state.get() == ImeState::Preedit {
// Leave preedit so that we also report the key-up for this key. // Leave preedit so that we also report the key-up for this key.
self.state.ime_state = ImeState::Ground; self.state.ime_state.set(ImeState::Ground);
} }
} }
} }
unsafe impl WinitView { unsafe impl WinitView {
#[sel(keyDown:)] #[sel(keyDown:)]
fn key_down(&mut self, event: &NSEvent) { fn key_down(&self, event: &NSEvent) {
trace_scope!("keyDown:"); trace_scope!("keyDown:");
let input_source = self.current_input_source(); {
if self.state.input_source != input_source && self.is_ime_enabled() { let mut prev_input_source = self.state.input_source.borrow_mut();
self.state.ime_state = ImeState::Disabled; let current_input_source = self.current_input_source();
self.state.input_source = input_source; if *prev_input_source != current_input_source && self.is_ime_enabled() {
self.queue_event(WindowEvent::Ime(Ime::Disabled)); *prev_input_source = current_input_source;
drop(prev_input_source);
self.state.ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled));
}
} }
// Get the characters from the event. // Get the characters from the event.
let old_ime_state = self.state.ime_state; let old_ime_state = self.state.ime_state.get();
self.state.forward_key_to_app = false; self.state.forward_key_to_app.set(false);
let event = replace_event(event, self.window().option_as_alt()); let event = replace_event(event, self.window().option_as_alt());
// The `interpretKeyEvents` function might call // The `interpretKeyEvents` function might call
@ -490,31 +484,31 @@ declare_class!(
// we must send the `KeyboardInput` event during IME if it triggered // we must send the `KeyboardInput` event during IME if it triggered
// `doCommandBySelector`. (doCommandBySelector means that the keyboard input // `doCommandBySelector`. (doCommandBySelector means that the keyboard input
// is not handled by IME and should be handled by the application) // is not handled by IME and should be handled by the application)
if self.state.ime_allowed { if self.state.ime_allowed.get() {
let events_for_nsview = NSArray::from_slice(&[event.copy()]); let events_for_nsview = NSArray::from_slice(&[event.copy()]);
unsafe { self.interpretKeyEvents(&events_for_nsview) }; unsafe { self.interpretKeyEvents(&events_for_nsview) };
// If the text was commited we must treat the next keyboard event as IME related. // If the text was commited we must treat the next keyboard event as IME related.
if self.state.ime_state == ImeState::Commited { if self.state.ime_state.get() == ImeState::Commited {
// Remove any marked text, so normal input can continue. // Remove any marked text, so normal input can continue.
*self.marked_text = NSMutableAttributedString::new(); *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
} }
} }
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
let had_ime_input = match self.state.ime_state { let had_ime_input = match self.state.ime_state.get() {
ImeState::Commited => { ImeState::Commited => {
// Allow normal input after the commit. // Allow normal input after the commit.
self.state.ime_state = ImeState::Ground; self.state.ime_state.set(ImeState::Ground);
true true
} }
ImeState::Preedit => true, ImeState::Preedit => true,
// `key_down` could result in preedit clear, so compare old and current state. // `key_down` could result in preedit clear, so compare old and current state.
_ => old_ime_state != self.state.ime_state, _ => old_ime_state != self.state.ime_state.get(),
}; };
if !had_ime_input || self.state.forward_key_to_app { if !had_ime_input || self.state.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, event.is_a_repeat(), None);
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID, device_id: DEVICE_ID,
@ -525,14 +519,17 @@ declare_class!(
} }
#[sel(keyUp:)] #[sel(keyUp:)]
fn key_up(&mut self, event: &NSEvent) { fn key_up(&self, event: &NSEvent) {
trace_scope!("keyUp:"); trace_scope!("keyUp:");
let event = replace_event(event, self.window().option_as_alt()); let event = replace_event(event, self.window().option_as_alt());
self.update_modifiers(&event, false); self.update_modifiers(&event, false);
// We want to send keyboard input when we are currently in the ground state. // We want to send keyboard input when we are currently in the ground state.
if matches!(self.state.ime_state, ImeState::Ground | ImeState::Disabled) { if matches!(
self.state.ime_state.get(),
ImeState::Ground | ImeState::Disabled
) {
self.queue_event(WindowEvent::KeyboardInput { self.queue_event(WindowEvent::KeyboardInput {
device_id: DEVICE_ID, device_id: DEVICE_ID,
event: create_key_event(&event, false, false, None), event: create_key_event(&event, false, false, None),
@ -542,7 +539,7 @@ declare_class!(
} }
#[sel(flagsChanged:)] #[sel(flagsChanged:)]
fn flags_changed(&mut self, ns_event: &NSEvent) { fn flags_changed(&self, ns_event: &NSEvent) {
trace_scope!("flagsChanged:"); trace_scope!("flagsChanged:");
self.update_modifiers(ns_event, true); self.update_modifiers(ns_event, true);
@ -573,7 +570,7 @@ declare_class!(
// Allows us to receive Cmd-. (the shortcut for closing a dialog) // Allows us to receive Cmd-. (the shortcut for closing a dialog)
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 // https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6
#[sel(cancelOperation:)] #[sel(cancelOperation:)]
fn cancel_operation(&mut self, _sender: *const Object) { fn cancel_operation(&self, _sender: *const Object) {
trace_scope!("cancelOperation:"); trace_scope!("cancelOperation:");
let event = NSApp() let event = NSApp()
@ -591,42 +588,42 @@ declare_class!(
} }
#[sel(mouseDown:)] #[sel(mouseDown:)]
fn mouse_down(&mut self, event: &NSEvent) { fn mouse_down(&self, event: &NSEvent) {
trace_scope!("mouseDown:"); trace_scope!("mouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[sel(mouseUp:)] #[sel(mouseUp:)]
fn mouse_up(&mut self, event: &NSEvent) { fn mouse_up(&self, event: &NSEvent) {
trace_scope!("mouseUp:"); trace_scope!("mouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[sel(rightMouseDown:)] #[sel(rightMouseDown:)]
fn right_mouse_down(&mut self, event: &NSEvent) { fn right_mouse_down(&self, event: &NSEvent) {
trace_scope!("rightMouseDown:"); trace_scope!("rightMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[sel(rightMouseUp:)] #[sel(rightMouseUp:)]
fn right_mouse_up(&mut self, event: &NSEvent) { fn right_mouse_up(&self, event: &NSEvent) {
trace_scope!("rightMouseUp:"); trace_scope!("rightMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
} }
#[sel(otherMouseDown:)] #[sel(otherMouseDown:)]
fn other_mouse_down(&mut self, event: &NSEvent) { fn other_mouse_down(&self, event: &NSEvent) {
trace_scope!("otherMouseDown:"); trace_scope!("otherMouseDown:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Pressed); self.mouse_click(event, ElementState::Pressed);
} }
#[sel(otherMouseUp:)] #[sel(otherMouseUp:)]
fn other_mouse_up(&mut self, event: &NSEvent) { fn other_mouse_up(&self, event: &NSEvent) {
trace_scope!("otherMouseUp:"); trace_scope!("otherMouseUp:");
self.mouse_motion(event); self.mouse_motion(event);
self.mouse_click(event, ElementState::Released); self.mouse_click(event, ElementState::Released);
@ -635,22 +632,22 @@ declare_class!(
// No tracing on these because that would be overly verbose // No tracing on these because that would be overly verbose
#[sel(mouseMoved:)] #[sel(mouseMoved:)]
fn mouse_moved(&mut self, event: &NSEvent) { fn mouse_moved(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[sel(mouseDragged:)] #[sel(mouseDragged:)]
fn mouse_dragged(&mut self, event: &NSEvent) { fn mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[sel(rightMouseDragged:)] #[sel(rightMouseDragged:)]
fn right_mouse_dragged(&mut self, event: &NSEvent) { fn right_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
#[sel(otherMouseDragged:)] #[sel(otherMouseDragged:)]
fn other_mouse_dragged(&mut self, event: &NSEvent) { fn other_mouse_dragged(&self, event: &NSEvent) {
self.mouse_motion(event); self.mouse_motion(event);
} }
@ -672,7 +669,7 @@ declare_class!(
} }
#[sel(scrollWheel:)] #[sel(scrollWheel:)]
fn scroll_wheel(&mut self, event: &NSEvent) { fn scroll_wheel(&self, event: &NSEvent) {
trace_scope!("scrollWheel:"); trace_scope!("scrollWheel:");
self.mouse_motion(event); self.mouse_motion(event);
@ -767,7 +764,7 @@ declare_class!(
} }
#[sel(pressureChangeWithEvent:)] #[sel(pressureChangeWithEvent:)]
fn pressure_change_with_event(&mut self, event: &NSEvent) { fn pressure_change_with_event(&self, event: &NSEvent) {
trace_scope!("pressureChangeWithEvent:"); trace_scope!("pressureChangeWithEvent:");
self.mouse_motion(event); self.mouse_motion(event);
@ -791,7 +788,7 @@ declare_class!(
#[sel(acceptsFirstMouse:)] #[sel(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> bool { fn accepts_first_mouse(&self, _event: &NSEvent) -> bool {
trace_scope!("acceptsFirstMouse:"); trace_scope!("acceptsFirstMouse:");
*self.accepts_first_mouse self.state.accepts_first_mouse
} }
} }
); );
@ -841,7 +838,7 @@ impl WinitView {
} }
fn is_ime_enabled(&self) -> bool { fn is_ime_enabled(&self) -> bool {
!matches!(self.state.ime_state, ImeState::Disabled) !matches!(self.state.ime_state.get(), ImeState::Disabled)
} }
fn current_input_source(&self) -> String { fn current_input_source(&self) -> String {
@ -852,43 +849,68 @@ impl WinitView {
.unwrap_or_else(String::new) .unwrap_or_else(String::new)
} }
pub(super) fn set_ime_allowed(&mut self, ime_allowed: bool) { pub(super) fn set_cursor_icon(&self, icon: Id<NSCursor, Shared>) {
if self.state.ime_allowed == ime_allowed { let mut cursor_state = self.state.cursor_state.borrow_mut();
cursor_state.cursor = icon;
}
/// Set whether the cursor should be visible or not.
///
/// Returns whether the state changed.
pub(super) fn set_cursor_visible(&self, visible: bool) -> bool {
let mut cursor_state = self.state.cursor_state.borrow_mut();
if visible != cursor_state.visible {
cursor_state.visible = visible;
true
} else {
false
}
}
pub(super) fn set_ime_allowed(&self, ime_allowed: bool) {
if self.state.ime_allowed.get() == ime_allowed {
return; return;
} }
self.state.ime_allowed = ime_allowed; self.state.ime_allowed.set(ime_allowed);
if self.state.ime_allowed { if self.state.ime_allowed.get() {
return; return;
} }
// Clear markedText // Clear markedText
*self.marked_text = NSMutableAttributedString::new(); *self.state.marked_text.borrow_mut() = NSMutableAttributedString::new();
if self.state.ime_state != ImeState::Disabled { if self.state.ime_state.get() != ImeState::Disabled {
self.state.ime_state = ImeState::Disabled; self.state.ime_state.set(ImeState::Disabled);
self.queue_event(WindowEvent::Ime(Ime::Disabled)); self.queue_event(WindowEvent::Ime(Ime::Disabled));
} }
} }
pub(super) fn set_ime_cursor_area( pub(super) fn set_ime_cursor_area(
&mut self, &self,
position: LogicalPosition<f64>, position: LogicalPosition<f64>,
size: LogicalSize<f64>, size: LogicalSize<f64>,
) { ) {
self.state.ime_position = position; self.state.ime_position.set(position);
self.state.ime_size = size; self.state.ime_size.set(size);
let input_context = self.inputContext().expect("input context"); let input_context = self.inputContext().expect("input context");
input_context.invalidateCharacterCoordinates(); input_context.invalidateCharacterCoordinates();
} }
// Update `state.modifiers` if `event` has something different /// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
fn update_modifiers(&mut self, ns_event: &NSEvent, is_flags_changed_event: bool) { pub(super) fn reset_modifiers(&self) {
if !self.state.modifiers.get().state().is_empty() {
self.state.modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
}
}
/// Update modifiers if `event` has something different
fn update_modifiers(&self, ns_event: &NSEvent, is_flags_changed_event: bool) {
use ElementState::{Pressed, Released}; use ElementState::{Pressed, Released};
let current_modifiers = event_mods(ns_event); let current_modifiers = event_mods(ns_event);
let prev_modifiers = self.state.modifiers; let prev_modifiers = self.state.modifiers.get();
self.state.modifiers.set(current_modifiers);
self.state.modifiers = current_modifiers;
// This function was called form the flagsChanged event, which is triggered // This function was called form the flagsChanged event, which is triggered
// when the user presses/releases a modifier even if the same kind of modifier // when the user presses/releases a modifier even if the same kind of modifier
@ -907,9 +929,8 @@ impl WinitView {
event.location = code_to_location(keycode); event.location = code_to_location(keycode);
let location_mask = ModLocationMask::from_location(event.location); let location_mask = ModLocationMask::from_location(event.location);
let phys_mod = self let mut phys_mod_state = self.state.phys_modifiers.borrow_mut();
.state let phys_mod = phys_mod_state
.phys_modifiers
.entry(key) .entry(key)
.or_insert(ModLocationMask::empty()); .or_insert(ModLocationMask::empty());
@ -975,6 +996,8 @@ impl WinitView {
}); });
} }
drop(phys_mod_state);
for event in events { for event in events {
self.queue_event(event); self.queue_event(event);
} }
@ -984,10 +1007,10 @@ impl WinitView {
return; return;
} }
self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers)); self.queue_event(WindowEvent::ModifiersChanged(self.state.modifiers.get()));
} }
fn mouse_click(&mut self, event: &NSEvent, button_state: ElementState) { fn mouse_click(&self, event: &NSEvent, button_state: ElementState) {
let button = mouse_button(event); let button = mouse_button(event);
self.update_modifiers(event, false); self.update_modifiers(event, false);
@ -999,7 +1022,7 @@ impl WinitView {
}); });
} }
fn mouse_motion(&mut self, event: &NSEvent) { fn mouse_motion(&self, event: &NSEvent) {
let window_point = event.locationInWindow(); let window_point = event.locationInWindow();
let view_point = self.convertPoint_fromView(window_point, None); let view_point = self.convertPoint_fromView(window_point, None);
let view_rect = self.frame(); let view_rect = self.frame();

View file

@ -1,14 +1,11 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::{ use std::collections::VecDeque;
collections::VecDeque, use std::f64;
f64, ops, use std::ops;
os::raw::c_void, use std::os::raw::c_void;
sync::{ use std::ptr::NonNull;
atomic::{AtomicBool, Ordering}, use std::sync::{Mutex, MutexGuard};
Mutex, MutexGuard,
},
};
use raw_window_handle::{ use raw_window_handle::{
AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle, AppKitDisplayHandle, AppKitWindowHandle, RawDisplayHandle, RawWindowHandle,
@ -43,7 +40,7 @@ use objc2::foundation::{
is_main_thread, CGFloat, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize, is_main_thread, CGFloat, NSArray, NSCopying, NSInteger, NSObject, NSPoint, NSRect, NSSize,
NSString, NSString,
}; };
use objc2::rc::{autoreleasepool, Id, Owned, Shared}; use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType}; use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType};
use super::appkit::{ use super::appkit::{
@ -110,9 +107,7 @@ declare_class!(
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct WinitWindow { pub(crate) struct WinitWindow {
// TODO: Fix unnecessary boxing here // TODO: Fix unnecessary boxing here
// SAFETY: These are initialized in WinitWindow::new, right after it is created.
shared_state: IvarDrop<Box<Mutex<SharedState>>>, shared_state: IvarDrop<Box<Mutex<SharedState>>>,
decorations: IvarDrop<Box<AtomicBool>>,
} }
unsafe impl ClassType for WinitWindow { unsafe impl ClassType for WinitWindow {
@ -120,6 +115,40 @@ declare_class!(
type Super = NSWindow; type Super = NSWindow;
} }
unsafe impl WinitWindow {
#[sel(initWithContentRect:styleMask:state:)]
unsafe fn init(
&mut self,
frame: NSRect,
mask: NSWindowStyleMask,
state: *mut c_void,
) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe {
msg_send![
super(self),
initWithContentRect: frame,
styleMask: mask,
backing: NSBackingStoreType::NSBackingStoreBuffered,
defer: false,
]
};
this.map(|this| {
// SAFETY: The pointer originally came from `Box::into_raw`.
Ivar::write(&mut this.shared_state, unsafe {
Box::from_raw(state as *mut Mutex<SharedState>)
});
// It is imperative to correct memory management that we
// disable the extra release that would otherwise happen when
// calling `clone` on the window.
this.setReleasedWhenClosed(false);
NonNull::from(this)
})
}
}
unsafe impl WinitWindow { unsafe impl WinitWindow {
#[sel(canBecomeMainWindow)] #[sel(canBecomeMainWindow)]
fn can_become_main_window(&self) -> bool { fn can_become_main_window(&self) -> bool {
@ -164,6 +193,8 @@ pub struct SharedState {
pub(crate) resize_increments: NSSize, pub(crate) resize_increments: NSSize,
/// The state of the `Option` as `Alt`. /// The state of the `Option` as `Alt`.
pub(crate) option_as_alt: OptionAsAlt, pub(crate) option_as_alt: OptionAsAlt,
decorations: bool,
} }
impl SharedState { impl SharedState {
@ -292,91 +323,83 @@ impl WinitWindow {
masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask;
} }
let this: Option<Id<Self, Owned>> = unsafe { let state = SharedState {
resizable: attrs.resizable,
maximized: attrs.maximized,
decorations: attrs.decorations,
..Default::default()
};
// Pass the state through FFI to the method declared on the class
let state_ptr: *mut c_void = Box::into_raw(Box::new(Mutex::new(state))).cast();
let this: Option<Id<Self, Shared>> = unsafe {
msg_send_id![ msg_send_id![
msg_send_id![WinitWindow::class(), alloc], msg_send_id![WinitWindow::class(), alloc],
initWithContentRect: frame, initWithContentRect: frame,
styleMask: masks, styleMask: masks,
backing: NSBackingStoreType::NSBackingStoreBuffered, state: state_ptr,
defer: false,
] ]
}; };
let this = this?;
this.map(|mut this| { let resize_increments = match attrs
let resize_increments = match attrs .resize_increments
.resize_increments .map(|i| i.to_logical::<f64>(this.scale_factor()))
.map(|i| i.to_logical::<f64>(this.scale_factor())) {
{ Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => {
Some(LogicalSize { width, height }) if width >= 1. && height >= 1. => { NSSize::new(width, height)
NSSize::new(width, height)
}
_ => NSSize::new(1., 1.),
};
// Properly initialize the window's variables
//
// Ideally this should be done in an `init` method,
// but for convenience we do it here instead.
let state = SharedState {
resizable: attrs.resizable,
maximized: attrs.maximized,
resize_increments,
..Default::default()
};
Ivar::write(&mut this.shared_state, Box::new(Mutex::new(state)));
Ivar::write(
&mut this.decorations,
Box::new(AtomicBool::new(attrs.decorations)),
);
this.setReleasedWhenClosed(false);
this.setTitle(&NSString::from_str(&attrs.title));
this.setAcceptsMouseMovedEvents(true);
if attrs.content_protected {
this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
} }
_ => NSSize::new(1., 1.),
};
if pl_attrs.titlebar_transparent { this.lock_shared_state("init").resize_increments = resize_increments;
this.setTitlebarAppearsTransparent(true);
} this.setTitle(&NSString::from_str(&attrs.title));
if pl_attrs.title_hidden { this.setAcceptsMouseMovedEvents(true);
this.setTitleVisibility(NSWindowTitleVisibility::Hidden);
} if attrs.content_protected {
if pl_attrs.titlebar_buttons_hidden { this.setSharingType(NSWindowSharingType::NSWindowSharingNone);
for titlebar_button in &[ }
#[allow(deprecated)]
NSWindowButton::FullScreen, if pl_attrs.titlebar_transparent {
NSWindowButton::Miniaturize, this.setTitlebarAppearsTransparent(true);
NSWindowButton::Close, }
NSWindowButton::Zoom, if pl_attrs.title_hidden {
] { this.setTitleVisibility(NSWindowTitleVisibility::Hidden);
if let Some(button) = this.standardWindowButton(*titlebar_button) { }
button.setHidden(true); if pl_attrs.titlebar_buttons_hidden {
} for titlebar_button in &[
#[allow(deprecated)]
NSWindowButton::FullScreen,
NSWindowButton::Miniaturize,
NSWindowButton::Close,
NSWindowButton::Zoom,
] {
if let Some(button) = this.standardWindowButton(*titlebar_button) {
button.setHidden(true);
} }
} }
if pl_attrs.movable_by_window_background { }
this.setMovableByWindowBackground(true); if pl_attrs.movable_by_window_background {
} this.setMovableByWindowBackground(true);
}
if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) { if let Some(button) = this.standardWindowButton(NSWindowButton::Zoom) {
button.setEnabled(false); button.setEnabled(false);
}
} }
}
if !pl_attrs.has_shadow { if !pl_attrs.has_shadow {
this.setHasShadow(false); this.setHasShadow(false);
} }
if attrs.position.is_none() { if attrs.position.is_none() {
this.center(); this.center();
} }
this.set_option_as_alt(pl_attrs.option_as_alt); this.set_option_as_alt(pl_attrs.option_as_alt);
Id::into_shared(this) Some(this)
})
}) })
.ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?; .ok_or_else(|| os_error!(OsError::CreationError("Couldn't create `NSWindow`")))?;
@ -753,9 +776,7 @@ impl WinitWindow {
pub fn set_cursor_icon(&self, icon: CursorIcon) { pub fn set_cursor_icon(&self, icon: CursorIcon) {
let view = self.view(); let view = self.view();
let mut cursor_state = view.state.cursor_state.lock().unwrap(); view.set_cursor_icon(NSCursor::from_icon(icon));
cursor_state.cursor = NSCursor::from_icon(icon);
drop(cursor_state);
self.invalidateCursorRectsForView(&view); self.invalidateCursorRectsForView(&view);
} }
@ -777,10 +798,8 @@ impl WinitWindow {
#[inline] #[inline]
pub fn set_cursor_visible(&self, visible: bool) { pub fn set_cursor_visible(&self, visible: bool) {
let view = self.view(); let view = self.view();
let mut cursor_state = view.state.cursor_state.lock().unwrap(); let state_changed = view.set_cursor_visible(visible);
if visible != cursor_state.visible { if state_changed {
cursor_state.visible = visible;
drop(cursor_state);
self.invalidateCursorRectsForView(&view); self.invalidateCursorRectsForView(&view);
} }
} }
@ -1097,45 +1116,44 @@ impl WinitWindow {
#[inline] #[inline]
pub fn set_decorations(&self, decorations: bool) { pub fn set_decorations(&self, decorations: bool) {
if decorations != self.decorations.load(Ordering::Acquire) { let mut shared_state_lock = self.lock_shared_state("set_decorations");
self.decorations.store(decorations, Ordering::Release); if decorations == shared_state_lock.decorations {
return;
let (fullscreen, resizable) = {
let shared_state_lock = self.lock_shared_state("set_decorations");
(
shared_state_lock.fullscreen.is_some(),
shared_state_lock.resizable,
)
};
// If we're in fullscreen mode, we wait to apply decoration changes
// until we're in `window_did_exit_fullscreen`.
if fullscreen {
return;
}
let new_mask = {
let mut new_mask = if decorations {
NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask
} else {
NSWindowStyleMask::NSBorderlessWindowMask
| NSWindowStyleMask::NSResizableWindowMask
};
if !resizable {
new_mask &= !NSWindowStyleMask::NSResizableWindowMask;
}
new_mask
};
self.set_style_mask_sync(new_mask);
} }
shared_state_lock.decorations = decorations;
let fullscreen = shared_state_lock.fullscreen.is_some();
let resizable = shared_state_lock.resizable;
drop(shared_state_lock);
// If we're in fullscreen mode, we wait to apply decoration changes
// until we're in `window_did_exit_fullscreen`.
if fullscreen {
return;
}
let new_mask = {
let mut new_mask = if decorations {
NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask
} else {
NSWindowStyleMask::NSBorderlessWindowMask | NSWindowStyleMask::NSResizableWindowMask
};
if !resizable {
new_mask &= !NSWindowStyleMask::NSResizableWindowMask;
}
new_mask
};
self.set_style_mask_sync(new_mask);
} }
#[inline] #[inline]
pub fn is_decorated(&self) -> bool { pub fn is_decorated(&self) -> bool {
self.decorations.load(Ordering::Acquire) self.lock_shared_state("is_decorated").decorations
} }
#[inline] #[inline]
@ -1170,8 +1188,7 @@ impl WinitWindow {
#[inline] #[inline]
pub fn set_ime_allowed(&self, allowed: bool) { pub fn set_ime_allowed(&self, allowed: bool) {
// TODO(madsmtm): Remove the need for this self.view().set_ime_allowed(allowed);
unsafe { Id::from_shared(self.view()) }.set_ime_allowed(allowed);
} }
#[inline] #[inline]
@ -1385,12 +1402,12 @@ impl WindowExtMacOS for WinitWindow {
} }
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) { fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
let mut shared_state_lock = self.shared_state.lock().unwrap(); let mut shared_state_lock = self.lock_shared_state("set_option_as_alt");
shared_state_lock.option_as_alt = option_as_alt; shared_state_lock.option_as_alt = option_as_alt;
} }
fn option_as_alt(&self) -> OptionAsAlt { fn option_as_alt(&self) -> OptionAsAlt {
let shared_state_lock = self.shared_state.lock().unwrap(); let shared_state_lock = self.lock_shared_state("option_as_alt");
shared_state_lock.option_as_alt shared_state_lock.option_as_alt
} }
} }

View file

@ -1,6 +1,6 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use std::cell::Cell;
use std::ptr; use std::ptr::{self, NonNull};
use objc2::declare::{Ivar, IvarDrop}; use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{NSArray, NSObject, NSSize, NSString}; use objc2::foundation::{NSArray, NSObject, NSSize, NSString};
@ -14,7 +14,6 @@ use super::appkit::{
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{Event, WindowEvent}, event::{Event, WindowEvent},
keyboard::ModifiersState,
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
event::{EventProxy, EventWrapper}, event::{EventProxy, EventWrapper},
@ -25,24 +24,28 @@ use crate::{
window::WindowId, window::WindowId,
}; };
#[derive(Debug)]
struct State {
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: Cell<bool>,
// During `windowDidResize`, we use this to only send Moved if the position changed.
previous_position: Cell<Option<(f64, f64)>>,
// Used to prevent redundant events.
previous_scale_factor: Cell<f64>,
}
declare_class!( declare_class!(
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct WinitWindowDelegate { pub(crate) struct WinitWindowDelegate {
window: IvarDrop<Id<WinitWindow, Shared>>, window: IvarDrop<Id<WinitWindow, Shared>>,
// TODO: It's possible for delegate methods to be called asynchronously, // TODO: It may be possible for delegate methods to be called
// causing data races / `RefCell` panics. // asynchronously, causing data races panics?
// This is set when WindowBuilder::with_fullscreen was set,
// see comments of `window_did_fail_to_enter_fullscreen`
initial_fullscreen: bool,
// During `windowDidResize`, we use this to only send Moved if the position changed.
// TODO: Remove unnecessary boxing here // TODO: Remove unnecessary boxing here
previous_position: IvarDrop<Option<Box<(f64, f64)>>>, state: IvarDrop<Box<State>>,
// Used to prevent redundant events.
previous_scale_factor: f64,
} }
unsafe impl ClassType for WinitWindowDelegate { unsafe impl ClassType for WinitWindowDelegate {
@ -51,19 +54,24 @@ declare_class!(
unsafe impl WinitWindowDelegate { unsafe impl WinitWindowDelegate {
#[sel(initWithWindow:initialFullscreen:)] #[sel(initWithWindow:initialFullscreen:)]
fn init_with_winit( unsafe fn init_with_winit(
&mut self, &mut self,
window: &WinitWindow, window: &WinitWindow,
initial_fullscreen: bool, initial_fullscreen: bool,
) -> Option<&mut Self> { ) -> Option<NonNull<Self>> {
let this: Option<&mut Self> = unsafe { msg_send![self, init] }; let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| { this.map(|this| {
let scale_factor = window.scale_factor(); let scale_factor = window.scale_factor();
Ivar::write(&mut this.window, window.retain()); Ivar::write(&mut this.window, window.retain());
Ivar::write(&mut this.initial_fullscreen, initial_fullscreen); Ivar::write(
Ivar::write(&mut this.previous_position, None); &mut this.state,
Ivar::write(&mut this.previous_scale_factor, scale_factor); Box::new(State {
initial_fullscreen: Cell::new(initial_fullscreen),
previous_position: Cell::new(None),
previous_scale_factor: Cell::new(scale_factor),
}),
);
if scale_factor != 1.0 { if scale_factor != 1.0 {
this.queue_static_scale_factor_changed_event(); this.queue_static_scale_factor_changed_event();
@ -85,7 +93,7 @@ declare_class!(
] ]
}; };
this NonNull::from(this)
}) })
} }
} }
@ -112,14 +120,14 @@ declare_class!(
} }
#[sel(windowDidResize:)] #[sel(windowDidResize:)]
fn window_did_resize(&mut self, _: Option<&Object>) { fn window_did_resize(&self, _: Option<&Object>) {
trace_scope!("windowDidResize:"); trace_scope!("windowDidResize:");
// NOTE: WindowEvent::Resized is reported in frameDidChange. // NOTE: WindowEvent::Resized is reported in frameDidChange.
self.emit_move_event(); self.emit_move_event();
} }
#[sel(windowWillStartLiveResize:)] #[sel(windowWillStartLiveResize:)]
fn window_will_start_live_resize(&mut self, _: Option<&Object>) { fn window_will_start_live_resize(&self, _: Option<&Object>) {
trace_scope!("windowWillStartLiveResize:"); trace_scope!("windowWillStartLiveResize:");
let increments = self let increments = self
@ -130,20 +138,20 @@ declare_class!(
} }
#[sel(windowDidEndLiveResize:)] #[sel(windowDidEndLiveResize:)]
fn window_did_end_live_resize(&mut self, _: Option<&Object>) { fn window_did_end_live_resize(&self, _: Option<&Object>) {
trace_scope!("windowDidEndLiveResize:"); trace_scope!("windowDidEndLiveResize:");
self.window.set_resize_increments_inner(NSSize::new(1., 1.)); self.window.set_resize_increments_inner(NSSize::new(1., 1.));
} }
// This won't be triggered if the move was part of a resize. // This won't be triggered if the move was part of a resize.
#[sel(windowDidMove:)] #[sel(windowDidMove:)]
fn window_did_move(&mut self, _: Option<&Object>) { fn window_did_move(&self, _: Option<&Object>) {
trace_scope!("windowDidMove:"); trace_scope!("windowDidMove:");
self.emit_move_event(); self.emit_move_event();
} }
#[sel(windowDidChangeBackingProperties:)] #[sel(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&mut self, _: Option<&Object>) { fn window_did_change_backing_properties(&self, _: Option<&Object>) {
trace_scope!("windowDidChangeBackingProperties:"); trace_scope!("windowDidChangeBackingProperties:");
self.queue_static_scale_factor_changed_event(); self.queue_static_scale_factor_changed_event();
} }
@ -166,17 +174,7 @@ declare_class!(
// NSWindowDelegate, and as a result a tracked modifiers state can quite // NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit // easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus. // a synthetic ModifiersChanged event when we lose focus.
self.window.view().reset_modifiers();
// TODO(madsmtm): Remove the need for this unsafety
let mut view = unsafe { Id::from_shared(self.window.view()) };
// Both update the state and emit a ModifiersChanged event.
if !view.state.modifiers.state().is_empty() {
view.state.modifiers = ModifiersState::empty().into();
self.queue_event(WindowEvent::ModifiersChanged(
ModifiersState::empty().into(),
));
}
self.queue_event(WindowEvent::Focused(false)); self.queue_event(WindowEvent::Focused(false));
} }
@ -307,9 +305,9 @@ declare_class!(
/// Invoked when entered fullscreen /// Invoked when entered fullscreen
#[sel(windowDidEnterFullScreen:)] #[sel(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&mut self, _: Option<&Object>) { fn window_did_enter_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowDidEnterFullScreen:"); trace_scope!("windowDidEnterFullScreen:");
*self.initial_fullscreen = false; self.state.initial_fullscreen.set(false);
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen"); let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
shared_state.in_fullscreen_transition = false; shared_state.in_fullscreen_transition = false;
let target_fullscreen = shared_state.target_fullscreen.take(); let target_fullscreen = shared_state.target_fullscreen.take();
@ -358,7 +356,7 @@ declare_class!(
.lock_shared_state("window_did_fail_to_enter_fullscreen"); .lock_shared_state("window_did_fail_to_enter_fullscreen");
shared_state.in_fullscreen_transition = false; shared_state.in_fullscreen_transition = false;
shared_state.target_fullscreen = None; shared_state.target_fullscreen = None;
if *self.initial_fullscreen { if self.state.initial_fullscreen.get() {
#[allow(clippy::let_unit_value)] #[allow(clippy::let_unit_value)]
unsafe { unsafe {
let _: () = msg_send![ let _: () = msg_send![
@ -448,13 +446,13 @@ impl WinitWindowDelegate {
AppState::queue_event(EventWrapper::StaticEvent(event)); AppState::queue_event(EventWrapper::StaticEvent(event));
} }
fn queue_static_scale_factor_changed_event(&mut self) { fn queue_static_scale_factor_changed_event(&self) {
let scale_factor = self.window.scale_factor(); let scale_factor = self.window.scale_factor();
if scale_factor == *self.previous_scale_factor { if scale_factor == self.state.previous_scale_factor.get() {
return; return;
}; };
*self.previous_scale_factor = scale_factor; self.state.previous_scale_factor.set(scale_factor);
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window: self.window.clone(), window: self.window.clone(),
suggested_size: self.view_size(), suggested_size: self.view_size(),
@ -463,12 +461,12 @@ impl WinitWindowDelegate {
AppState::queue_event(wrapper); AppState::queue_event(wrapper);
} }
fn emit_move_event(&mut self) { fn emit_move_event(&self) {
let rect = self.window.frame(); let rect = self.window.frame();
let x = rect.origin.x as f64; let x = rect.origin.x as f64;
let y = util::bottom_left_to_top_left(rect); let y = util::bottom_left_to_top_left(rect);
if self.previous_position.as_deref() != Some(&(x, y)) { if self.state.previous_position.get() != Some((x, y)) {
*self.previous_position = Some(Box::new((x, y))); self.state.previous_position.set(Some((x, y)));
let scale_factor = self.window.scale_factor(); let scale_factor = self.window.scale_factor();
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor); let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
self.queue_event(WindowEvent::Moved(physical_pos)); self.queue_event(WindowEvent::Moved(physical_pos));