Refactor macOS to use new objc2 features (#2465)
* Remove UnownedWindow::inner_rect * Refactor custom view to use much less `unsafe` The compiler fence is safe to get rid of now since `interpretKeyEvents` takes `&mut self` * Refactor Window to use much less unsafe * Refactor NSApplication usage to have much less unsafe * Remove cocoa dependency * Enable `deny(unsafe_op_in_unsafe_fn)` on macOS Also re-enable clippy `let_unit_value` lint * Remove #[macro_use] on macOS * Refactor window delegate to use much less unsafe
This commit is contained in:
parent
05dd31b8ea
commit
340f951d10
34 changed files with 2756 additions and 2335 deletions
|
|
@ -1,137 +1,44 @@
|
|||
use std::{
|
||||
f64,
|
||||
os::raw::c_void,
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use std::ptr;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow, NSWindowOcclusionState},
|
||||
base::{id, nil},
|
||||
};
|
||||
use objc2::foundation::{NSObject, NSUInteger};
|
||||
use objc2::rc::autoreleasepool;
|
||||
use objc2::declare::{Ivar, IvarDrop};
|
||||
use objc2::foundation::{NSArray, NSObject, NSString};
|
||||
use objc2::rc::{autoreleasepool, Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{declare_class, ClassType};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType};
|
||||
|
||||
use super::appkit::{
|
||||
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
|
||||
};
|
||||
use crate::{
|
||||
dpi::{LogicalPosition, LogicalSize},
|
||||
event::{Event, ModifiersState, WindowEvent},
|
||||
platform_impl::platform::{
|
||||
app_state::AppState,
|
||||
event::{EventProxy, EventWrapper},
|
||||
util::{self, IdRef},
|
||||
view::ViewState,
|
||||
window::{get_window_id, UnownedWindow},
|
||||
util,
|
||||
window::WinitWindow,
|
||||
},
|
||||
window::{Fullscreen, WindowId},
|
||||
};
|
||||
|
||||
struct WindowDelegateState {
|
||||
ns_window: IdRef, // never changes
|
||||
ns_view: IdRef, // never changes
|
||||
|
||||
window: Weak<UnownedWindow>,
|
||||
|
||||
// TODO: It's possible for delegate methods to be called asynchronously,
|
||||
// causing data races / `RefCell` 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.
|
||||
previous_position: Option<(f64, f64)>,
|
||||
|
||||
// Used to prevent redundant events.
|
||||
previous_scale_factor: f64,
|
||||
}
|
||||
|
||||
impl WindowDelegateState {
|
||||
fn new(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> Self {
|
||||
let scale_factor = window.scale_factor();
|
||||
let mut delegate_state = WindowDelegateState {
|
||||
ns_window: window.ns_window.clone(),
|
||||
ns_view: window.ns_view.clone(),
|
||||
window: Arc::downgrade(window),
|
||||
initial_fullscreen,
|
||||
previous_position: None,
|
||||
previous_scale_factor: scale_factor,
|
||||
};
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
delegate_state.emit_static_scale_factor_changed_event();
|
||||
}
|
||||
|
||||
delegate_state
|
||||
}
|
||||
|
||||
fn with_window<F, T>(&mut self, callback: F) -> Option<T>
|
||||
where
|
||||
F: FnOnce(&UnownedWindow) -> T,
|
||||
{
|
||||
self.window.upgrade().map(|ref window| callback(window))
|
||||
}
|
||||
|
||||
fn emit_event(&mut self, event: WindowEvent<'static>) {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(*self.ns_window)),
|
||||
event,
|
||||
};
|
||||
AppState::queue_event(EventWrapper::StaticEvent(event));
|
||||
}
|
||||
|
||||
fn emit_static_scale_factor_changed_event(&mut self) {
|
||||
let scale_factor = self.get_scale_factor();
|
||||
if scale_factor == self.previous_scale_factor {
|
||||
return;
|
||||
};
|
||||
|
||||
self.previous_scale_factor = scale_factor;
|
||||
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
ns_window: IdRef::retain(*self.ns_window),
|
||||
suggested_size: self.view_size(),
|
||||
scale_factor,
|
||||
});
|
||||
AppState::queue_event(wrapper);
|
||||
}
|
||||
|
||||
fn emit_move_event(&mut self) {
|
||||
let rect = unsafe { NSWindow::frame(*self.ns_window) };
|
||||
let x = rect.origin.x as f64;
|
||||
let y = util::bottom_left_to_top_left(rect);
|
||||
let moved = self.previous_position != Some((x, y));
|
||||
if moved {
|
||||
self.previous_position = Some((x, y));
|
||||
let scale_factor = self.get_scale_factor();
|
||||
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
|
||||
self.emit_event(WindowEvent::Moved(physical_pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_scale_factor(&self) -> f64 {
|
||||
(unsafe { NSWindow::backingScaleFactor(*self.ns_window) }) as f64
|
||||
}
|
||||
|
||||
fn view_size(&self) -> LogicalSize<f64> {
|
||||
let ns_size = unsafe { NSView::frame(*self.ns_view).size };
|
||||
LogicalSize::new(ns_size.width as f64, ns_size.height as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_delegate(window: &Arc<UnownedWindow>, initial_fullscreen: bool) -> IdRef {
|
||||
let state = WindowDelegateState::new(window, initial_fullscreen);
|
||||
unsafe {
|
||||
// This is free'd in `dealloc`
|
||||
let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void;
|
||||
let delegate: id = msg_send![WinitWindowDelegate::class(), alloc];
|
||||
IdRef::new(msg_send![delegate, initWithWinit: state_ptr])
|
||||
}
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
struct WinitWindowDelegate {
|
||||
state: *mut c_void,
|
||||
pub(crate) struct WinitWindowDelegate {
|
||||
window: IvarDrop<Id<WinitWindow, Shared>>,
|
||||
|
||||
// TODO: It's possible for delegate methods to be called asynchronously,
|
||||
// causing data races / `RefCell` 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
|
||||
previous_position: IvarDrop<Option<Box<(f64, f64)>>>,
|
||||
|
||||
// Used to prevent redundant events.
|
||||
previous_scale_factor: f64,
|
||||
}
|
||||
|
||||
unsafe impl ClassType for WinitWindowDelegate {
|
||||
|
|
@ -139,21 +46,28 @@ declare_class!(
|
|||
}
|
||||
|
||||
unsafe impl WinitWindowDelegate {
|
||||
#[sel(dealloc)]
|
||||
fn dealloc(&mut self) {
|
||||
self.with_state(|state| unsafe {
|
||||
drop(Box::from_raw(state as *mut WindowDelegateState));
|
||||
});
|
||||
}
|
||||
|
||||
#[sel(initWithWinit:)]
|
||||
fn init_with_winit(&mut self, state: *mut c_void) -> Option<&mut Self> {
|
||||
#[sel(initWithWindow:initialFullscreen:)]
|
||||
fn init_with_winit(
|
||||
&mut self,
|
||||
window: &WinitWindow,
|
||||
initial_fullscreen: bool,
|
||||
) -> Option<&mut Self> {
|
||||
let this: Option<&mut Self> = unsafe { msg_send![self, init] };
|
||||
this.map(|this| {
|
||||
*this.state = state;
|
||||
this.with_state(|state| {
|
||||
let _: () = unsafe { msg_send![*state.ns_window, setDelegate: &*this] };
|
||||
let scale_factor = window.scale_factor();
|
||||
|
||||
Ivar::write(&mut this.window, unsafe {
|
||||
let window: *const WinitWindow = window;
|
||||
Id::retain(window as *mut WinitWindow).unwrap()
|
||||
});
|
||||
Ivar::write(&mut this.initial_fullscreen, initial_fullscreen);
|
||||
Ivar::write(&mut this.previous_position, None);
|
||||
Ivar::write(&mut this.previous_scale_factor, scale_factor);
|
||||
|
||||
if scale_factor != 1.0 {
|
||||
this.emit_static_scale_factor_changed_event();
|
||||
}
|
||||
this.window.setDelegate(Some(this));
|
||||
this
|
||||
})
|
||||
}
|
||||
|
|
@ -162,221 +76,177 @@ declare_class!(
|
|||
// NSWindowDelegate + NSDraggingDestination protocols
|
||||
unsafe impl WinitWindowDelegate {
|
||||
#[sel(windowShouldClose:)]
|
||||
fn window_should_close(&self, _: id) -> bool {
|
||||
fn window_should_close(&self, _: Option<&Object>) -> bool {
|
||||
trace_scope!("windowShouldClose:");
|
||||
self.with_state(|state| state.emit_event(WindowEvent::CloseRequested));
|
||||
self.emit_event(WindowEvent::CloseRequested);
|
||||
false
|
||||
}
|
||||
|
||||
#[sel(windowWillClose:)]
|
||||
fn window_will_close(&self, _: id) {
|
||||
fn window_will_close(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowWillClose:");
|
||||
self.with_state(|state| unsafe {
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
// be called after the window closes.
|
||||
let _: () = msg_send![*state.ns_window, setDelegate: nil];
|
||||
});
|
||||
state.emit_event(WindowEvent::Destroyed);
|
||||
// `setDelegate:` retains the previous value and then autoreleases it
|
||||
autoreleasepool(|_| {
|
||||
// Since El Capitan, we need to be careful that delegate methods can't
|
||||
// be called after the window closes.
|
||||
self.window.setDelegate(None);
|
||||
});
|
||||
self.emit_event(WindowEvent::Destroyed);
|
||||
}
|
||||
|
||||
#[sel(windowDidResize:)]
|
||||
fn window_did_resize(&self, _: id) {
|
||||
fn window_did_resize(&mut self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidResize:");
|
||||
self.with_state(|state| {
|
||||
// NOTE: WindowEvent::Resized is reported in frameDidChange.
|
||||
state.emit_move_event();
|
||||
});
|
||||
// NOTE: WindowEvent::Resized is reported in frameDidChange.
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
// This won't be triggered if the move was part of a resize.
|
||||
#[sel(windowDidMove:)]
|
||||
fn window_did_move(&self, _: id) {
|
||||
fn window_did_move(&mut self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidMove:");
|
||||
self.with_state(|state| {
|
||||
state.emit_move_event();
|
||||
});
|
||||
self.emit_move_event();
|
||||
}
|
||||
|
||||
#[sel(windowDidChangeBackingProperties:)]
|
||||
fn window_did_change_backing_properties(&self, _: id) {
|
||||
fn window_did_change_backing_properties(&mut self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidChangeBackingProperties:");
|
||||
self.with_state(|state| {
|
||||
state.emit_static_scale_factor_changed_event();
|
||||
});
|
||||
self.emit_static_scale_factor_changed_event();
|
||||
}
|
||||
|
||||
#[sel(windowDidBecomeKey:)]
|
||||
fn window_did_become_key(&self, _: id) {
|
||||
fn window_did_become_key(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidBecomeKey:");
|
||||
self.with_state(|state| {
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
// lost focus
|
||||
state.emit_event(WindowEvent::Focused(true));
|
||||
});
|
||||
// TODO: center the cursor if the window had mouse grab when it
|
||||
// lost focus
|
||||
self.emit_event(WindowEvent::Focused(true));
|
||||
}
|
||||
|
||||
#[sel(windowDidResignKey:)]
|
||||
fn window_did_resign_key(&self, _: id) {
|
||||
fn window_did_resign_key(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidResignKey:");
|
||||
self.with_state(|state| {
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
||||
// being received when the modifiers are released. This is because
|
||||
// flagsChanged events are received by the NSView instead of the
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
//
|
||||
// Here we (very unsafely) acquire the winitState (a ViewState) from the
|
||||
// Object referenced by state.ns_view (an IdRef, which is dereferenced
|
||||
// to an id)
|
||||
let view_state: &mut ViewState = unsafe {
|
||||
let ns_view: &Object = (*state.ns_view).as_ref().expect("failed to deref");
|
||||
let state_ptr: *mut c_void = *ns_view.ivar("winitState");
|
||||
&mut *(state_ptr as *mut ViewState)
|
||||
};
|
||||
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
|
||||
// NSWindowDelegate will receive a didResignKey event despite no event
|
||||
// being received when the modifiers are released. This is because
|
||||
// flagsChanged events are received by the NSView instead of the
|
||||
// NSWindowDelegate, and as a result a tracked modifiers state can quite
|
||||
// easily fall out of synchrony with reality. This requires us to emit
|
||||
// a synthetic ModifiersChanged event when we lose focus.
|
||||
|
||||
// Both update the state and emit a ModifiersChanged event.
|
||||
if !view_state.modifiers.is_empty() {
|
||||
view_state.modifiers = ModifiersState::empty();
|
||||
state.emit_event(WindowEvent::ModifiersChanged(view_state.modifiers));
|
||||
}
|
||||
// TODO(madsmtm): Remove the need for this unsafety
|
||||
let mut view = unsafe { Id::from_shared(self.window.view()) };
|
||||
|
||||
state.emit_event(WindowEvent::Focused(false));
|
||||
});
|
||||
// Both update the state and emit a ModifiersChanged event.
|
||||
if !view.state.modifiers.is_empty() {
|
||||
view.state.modifiers = ModifiersState::empty();
|
||||
self.emit_event(WindowEvent::ModifiersChanged(view.state.modifiers));
|
||||
}
|
||||
|
||||
self.emit_event(WindowEvent::Focused(false));
|
||||
}
|
||||
|
||||
/// Invoked when the dragged image enters destination bounds or frame
|
||||
#[sel(draggingEntered:)]
|
||||
fn dragging_entered(&self, sender: id) -> bool {
|
||||
fn dragging_entered(&self, sender: *mut Object) -> bool {
|
||||
trace_scope!("draggingEntered:");
|
||||
|
||||
use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
|
||||
let filenames =
|
||||
unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
|
||||
let pb: Id<NSPasteboard, Shared> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames: Id<NSArray<NSString>, Shared> = unsafe { Id::cast(filenames) };
|
||||
|
||||
for file in unsafe { filenames.iter() } {
|
||||
use cocoa::foundation::NSString;
|
||||
use std::ffi::CStr;
|
||||
|
||||
unsafe {
|
||||
let f = NSString::UTF8String(file);
|
||||
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
|
||||
|
||||
self.with_state(|state| {
|
||||
state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path)));
|
||||
});
|
||||
}
|
||||
}
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.emit_event(WindowEvent::HoveredFile(path));
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the image is released
|
||||
#[sel(prepareForDragOperation:)]
|
||||
fn prepare_for_drag_operation(&self, _: id) -> bool {
|
||||
fn prepare_for_drag_operation(&self, _: Option<&Object>) -> bool {
|
||||
trace_scope!("prepareForDragOperation:");
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked after the released image has been removed from the screen
|
||||
#[sel(performDragOperation:)]
|
||||
fn perform_drag_operation(&self, sender: id) -> bool {
|
||||
fn perform_drag_operation(&self, sender: *mut Object) -> bool {
|
||||
trace_scope!("performDragOperation:");
|
||||
|
||||
use cocoa::{appkit::NSPasteboard, foundation::NSFastEnumeration};
|
||||
use std::path::PathBuf;
|
||||
|
||||
let pb: id = unsafe { msg_send![sender, draggingPasteboard] };
|
||||
let filenames =
|
||||
unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) };
|
||||
let pb: Id<NSPasteboard, Shared> = unsafe { msg_send_id![sender, draggingPasteboard] };
|
||||
let filenames = pb.propertyListForType(unsafe { NSFilenamesPboardType });
|
||||
let filenames: Id<NSArray<NSString>, Shared> = unsafe { Id::cast(filenames) };
|
||||
|
||||
for file in unsafe { filenames.iter() } {
|
||||
use cocoa::foundation::NSString;
|
||||
use std::ffi::CStr;
|
||||
|
||||
unsafe {
|
||||
let f = NSString::UTF8String(file);
|
||||
let path = CStr::from_ptr(f).to_string_lossy().into_owned();
|
||||
|
||||
self.with_state(|state| {
|
||||
state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path)));
|
||||
});
|
||||
}
|
||||
}
|
||||
filenames.into_iter().for_each(|file| {
|
||||
let path = PathBuf::from(file.to_string());
|
||||
self.emit_event(WindowEvent::DroppedFile(path));
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is complete
|
||||
#[sel(concludeDragOperation:)]
|
||||
fn conclude_drag_operation(&self, _: id) {
|
||||
fn conclude_drag_operation(&self, _: Option<&Object>) {
|
||||
trace_scope!("concludeDragOperation:");
|
||||
}
|
||||
|
||||
/// Invoked when the dragging operation is cancelled
|
||||
#[sel(draggingExited:)]
|
||||
fn dragging_exited(&self, _: id) {
|
||||
fn dragging_exited(&self, _: Option<&Object>) {
|
||||
trace_scope!("draggingExited:");
|
||||
self.with_state(|state| state.emit_event(WindowEvent::HoveredFileCancelled));
|
||||
self.emit_event(WindowEvent::HoveredFileCancelled);
|
||||
}
|
||||
|
||||
/// Invoked when before enter fullscreen
|
||||
#[sel(windowWillEnterFullscreen:)]
|
||||
fn window_will_enter_fullscreen(&self, _: id) {
|
||||
fn window_will_enter_fullscreen(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowWillEnterFullscreen:");
|
||||
|
||||
self.with_state(|state| {
|
||||
state.with_window(|window| {
|
||||
let mut shared_state = window.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = Some(window.current_monitor_inner());
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
})
|
||||
});
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_enter_fullscreen");
|
||||
shared_state.maximized = self.window.is_zoomed();
|
||||
let fullscreen = shared_state.fullscreen.as_ref();
|
||||
match fullscreen {
|
||||
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||
// can't enter exclusive mode by other means (like the
|
||||
// fullscreen button on the window decorations)
|
||||
Some(Fullscreen::Exclusive(_)) => (),
|
||||
// `window_will_enter_fullscreen` was triggered and we're already
|
||||
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||
// as it updates the state
|
||||
Some(Fullscreen::Borderless(_)) => (),
|
||||
// Otherwise, we must've reached fullscreen by the user clicking
|
||||
// on the green fullscreen button. Update state!
|
||||
None => {
|
||||
let current_monitor = Some(self.window.current_monitor_inner());
|
||||
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
|
||||
}
|
||||
}
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
/// Invoked when before exit fullscreen
|
||||
#[sel(windowWillExitFullScreen:)]
|
||||
fn window_will_exit_fullscreen(&self, _: id) {
|
||||
fn window_will_exit_fullscreen(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowWillExitFullScreen:");
|
||||
|
||||
self.with_state(|state| {
|
||||
state.with_window(|window| {
|
||||
let mut shared_state = window.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
});
|
||||
});
|
||||
let mut shared_state = self.window.lock_shared_state("window_will_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
#[sel(window:willUseFullScreenPresentationOptions:)]
|
||||
fn window_will_use_fullscreen_presentation_options(
|
||||
&self,
|
||||
_: id,
|
||||
proposed_options: NSUInteger,
|
||||
) -> NSUInteger {
|
||||
_: Option<&Object>,
|
||||
proposed_options: NSApplicationPresentationOptions,
|
||||
) -> NSApplicationPresentationOptions {
|
||||
trace_scope!("window:willUseFullScreenPresentationOptions:");
|
||||
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||
// this would be configurable by the user. Unfortunately because of our
|
||||
|
|
@ -386,58 +256,46 @@ declare_class!(
|
|||
// still want to make this configurable for borderless fullscreen. Right now
|
||||
// we don't, for consistency. If we do, it should be documented that the
|
||||
// user-provided options are ignored in exclusive fullscreen.
|
||||
let mut options: NSUInteger = proposed_options;
|
||||
self.with_state(|state| {
|
||||
state.with_window(|window| {
|
||||
let shared_state =
|
||||
window.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = (NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
|
||||
.bits() as NSUInteger;
|
||||
}
|
||||
})
|
||||
});
|
||||
let mut options = proposed_options;
|
||||
let shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_will_use_fullscreen_presentation_options");
|
||||
if let Some(Fullscreen::Exclusive(_)) = shared_state.fullscreen {
|
||||
options = NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
|
||||
/// Invoked when entered fullscreen
|
||||
#[sel(windowDidEnterFullscreen:)]
|
||||
fn window_did_enter_fullscreen(&self, _: id) {
|
||||
fn window_did_enter_fullscreen(&mut self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidEnterFullscreen:");
|
||||
self.with_state(|state| {
|
||||
state.initial_fullscreen = false;
|
||||
state.with_window(|window| {
|
||||
let mut shared_state = window.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
});
|
||||
});
|
||||
*self.initial_fullscreen = false;
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when exited fullscreen
|
||||
#[sel(windowDidExitFullscreen:)]
|
||||
fn window_did_exit_fullscreen(&self, _: id) {
|
||||
fn window_did_exit_fullscreen(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidExitFullscreen:");
|
||||
|
||||
self.with_state(|state| {
|
||||
state.with_window(|window| {
|
||||
window.restore_state_from_fullscreen();
|
||||
let mut shared_state = window.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
})
|
||||
});
|
||||
self.window.restore_state_from_fullscreen();
|
||||
let mut shared_state = self.window.lock_shared_state("window_did_exit_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
let target_fullscreen = shared_state.target_fullscreen.take();
|
||||
drop(shared_state);
|
||||
if let Some(target_fullscreen) = target_fullscreen {
|
||||
self.window.set_fullscreen(target_fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
/// Invoked when fail to enter fullscreen
|
||||
|
|
@ -457,55 +315,90 @@ declare_class!(
|
|||
/// This method indicates that there was an error, and you should clean up any
|
||||
/// work you may have done to prepare to enter full-screen mode.
|
||||
#[sel(windowDidFailToEnterFullscreen:)]
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: id) {
|
||||
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidFailToEnterFullscreen:");
|
||||
self.with_state(|state| {
|
||||
state.with_window(|window| {
|
||||
let mut shared_state =
|
||||
window.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
});
|
||||
if state.initial_fullscreen {
|
||||
unsafe {
|
||||
let _: () = msg_send![*state.ns_window,
|
||||
performSelector:sel!(toggleFullScreen:)
|
||||
withObject:nil
|
||||
afterDelay: 0.5
|
||||
];
|
||||
};
|
||||
} else {
|
||||
state.with_window(|window| window.restore_state_from_fullscreen());
|
||||
}
|
||||
});
|
||||
let mut shared_state = self
|
||||
.window
|
||||
.lock_shared_state("window_did_fail_to_enter_fullscreen");
|
||||
shared_state.in_fullscreen_transition = false;
|
||||
shared_state.target_fullscreen = None;
|
||||
if *self.initial_fullscreen {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
&*self.window,
|
||||
performSelector: sel!(toggleFullScreen:),
|
||||
withObject: ptr::null::<Object>(),
|
||||
afterDelay: 0.5,
|
||||
];
|
||||
};
|
||||
} else {
|
||||
self.window.restore_state_from_fullscreen();
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when the occlusion state of the window changes
|
||||
#[sel(windowDidChangeOcclusionState:)]
|
||||
fn window_did_change_occlusion_state(&self, _: id) {
|
||||
fn window_did_change_occlusion_state(&self, _: Option<&Object>) {
|
||||
trace_scope!("windowDidChangeOcclusionState:");
|
||||
unsafe {
|
||||
self.with_state(|state| {
|
||||
state.emit_event(WindowEvent::Occluded(
|
||||
!state
|
||||
.ns_window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
|
||||
))
|
||||
});
|
||||
}
|
||||
self.emit_event(WindowEvent::Occluded(
|
||||
!self
|
||||
.window
|
||||
.occlusionState()
|
||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
|
||||
))
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
impl WinitWindowDelegate {
|
||||
// This function is definitely unsafe (&self -> &mut state), but labeling that
|
||||
// would increase boilerplate and wouldn't really clarify anything...
|
||||
fn with_state<F: FnOnce(&mut WindowDelegateState) -> T, T>(&self, callback: F) {
|
||||
let state_ptr = unsafe {
|
||||
let state_ptr: *mut c_void = *self.state;
|
||||
&mut *(state_ptr as *mut WindowDelegateState)
|
||||
pub fn new(window: &WinitWindow, initial_fullscreen: bool) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![Self::class(), alloc],
|
||||
initWithWindow: window,
|
||||
initialFullscreen: initial_fullscreen,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_event(&self, event: WindowEvent<'static>) {
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(self.window.id()),
|
||||
event,
|
||||
};
|
||||
callback(state_ptr);
|
||||
AppState::queue_event(EventWrapper::StaticEvent(event));
|
||||
}
|
||||
|
||||
fn emit_static_scale_factor_changed_event(&mut self) {
|
||||
let scale_factor = self.window.scale_factor();
|
||||
if scale_factor == *self.previous_scale_factor {
|
||||
return;
|
||||
};
|
||||
|
||||
*self.previous_scale_factor = scale_factor;
|
||||
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
||||
window: self.window.clone(),
|
||||
suggested_size: self.view_size(),
|
||||
scale_factor,
|
||||
});
|
||||
AppState::queue_event(wrapper);
|
||||
}
|
||||
|
||||
fn emit_move_event(&mut self) {
|
||||
let rect = self.window.frame();
|
||||
let x = rect.origin.x as f64;
|
||||
let y = util::bottom_left_to_top_left(rect);
|
||||
if self.previous_position.as_deref() != Some(&(x, y)) {
|
||||
*self.previous_position = Some(Box::new((x, y)));
|
||||
let scale_factor = self.window.scale_factor();
|
||||
let physical_pos = LogicalPosition::<f64>::from((x, y)).to_physical(scale_factor);
|
||||
self.emit_event(WindowEvent::Moved(physical_pos));
|
||||
}
|
||||
}
|
||||
|
||||
fn view_size(&self) -> LogicalSize<f64> {
|
||||
let size = self.window.contentView().frame().size;
|
||||
LogicalSize::new(size.width as f64, size.height as f64)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue