#![allow(clippy::unnecessary_cast)]
use std::ptr;
use objc2::declare::{Ivar, IvarDrop};
use objc2::foundation::{NSArray, NSObject, NSSize, NSString};
use objc2::rc::{autoreleasepool, Id, Shared};
use objc2::runtime::Object;
use objc2::{class, 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,
window::{get_ns_theme, WinitWindow},
Fullscreen,
},
window::WindowId,
};
declare_class!(
#[derive(Debug)]
pub(crate) struct WinitWindowDelegate {
window: IvarDrop<Id<WinitWindow, Shared>>,
initial_fullscreen: bool,
previous_position: IvarDrop<Option<Box<(f64, f64)>>>,
previous_scale_factor: f64,
}
unsafe impl ClassType for WinitWindowDelegate {
type Super = NSObject;
}
unsafe impl WinitWindowDelegate {
#[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| {
let scale_factor = window.scale_factor();
Ivar::write(&mut this.window, window.retain());
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.queue_static_scale_factor_changed_event();
}
this.window.setDelegate(Some(this));
let notification_center: Id<Object, Shared> =
unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] };
let notification_name =
NSString::from_str("AppleInterfaceThemeChangedNotification");
let _: () = unsafe {
msg_send![
¬ification_center,
addObserver: &*this
selector: sel!(effectiveAppearanceDidChange:)
name: &*notification_name
object: ptr::null::<Object>()
]
};
this
})
}
}
unsafe impl WinitWindowDelegate {
#[sel(windowShouldClose:)]
fn window_should_close(&self, _: Option<&Object>) -> bool {
trace_scope!("windowShouldClose:");
self.queue_event(WindowEvent::CloseRequested);
false
}
#[sel(windowWillClose:)]
fn window_will_close(&self, _: Option<&Object>) {
trace_scope!("windowWillClose:");
autoreleasepool(|_| {
self.window.setDelegate(None);
});
self.queue_event(WindowEvent::Destroyed);
}
#[sel(windowDidResize:)]
fn window_did_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowDidResize:");
self.emit_move_event();
}
#[sel(windowWillStartLiveResize:)]
fn window_will_start_live_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowWillStartLiveResize:");
let increments = self
.window
.lock_shared_state("window_will_enter_fullscreen")
.resize_increments;
self.window.set_resize_increments_inner(increments);
}
#[sel(windowDidEndLiveResize:)]
fn window_did_end_live_resize(&mut self, _: Option<&Object>) {
trace_scope!("windowDidEndLiveResize:");
self.window.set_resize_increments_inner(NSSize::new(1., 1.));
}
#[sel(windowDidMove:)]
fn window_did_move(&mut self, _: Option<&Object>) {
trace_scope!("windowDidMove:");
self.emit_move_event();
}
#[sel(windowDidChangeBackingProperties:)]
fn window_did_change_backing_properties(&mut self, _: Option<&Object>) {
trace_scope!("windowDidChangeBackingProperties:");
self.queue_static_scale_factor_changed_event();
}
#[sel(windowDidBecomeKey:)]
fn window_did_become_key(&self, _: Option<&Object>) {
trace_scope!("windowDidBecomeKey:");
self.queue_event(WindowEvent::Focused(true));
}
#[sel(windowDidResignKey:)]
fn window_did_resign_key(&self, _: Option<&Object>) {
trace_scope!("windowDidResignKey:");
let mut view = unsafe { Id::from_shared(self.window.view()) };
if !view.state.modifiers.is_empty() {
view.state.modifiers = ModifiersState::empty();
self.queue_event(WindowEvent::ModifiersChanged(view.state.modifiers));
}
self.queue_event(WindowEvent::Focused(false));
}
#[sel(draggingEntered:)]
fn dragging_entered(&self, sender: *mut Object) -> bool {
trace_scope!("draggingEntered:");
use std::path::PathBuf;
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) };
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::HoveredFile(path));
});
true
}
#[sel(prepareForDragOperation:)]
fn prepare_for_drag_operation(&self, _: Option<&Object>) -> bool {
trace_scope!("prepareForDragOperation:");
true
}
#[sel(performDragOperation:)]
fn perform_drag_operation(&self, sender: *mut Object) -> bool {
trace_scope!("performDragOperation:");
use std::path::PathBuf;
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) };
filenames.into_iter().for_each(|file| {
let path = PathBuf::from(file.to_string());
self.queue_event(WindowEvent::DroppedFile(path));
});
true
}
#[sel(concludeDragOperation:)]
fn conclude_drag_operation(&self, _: Option<&Object>) {
trace_scope!("concludeDragOperation:");
}
#[sel(draggingExited:)]
fn dragging_exited(&self, _: Option<&Object>) {
trace_scope!("draggingExited:");
self.queue_event(WindowEvent::HoveredFileCancelled);
}
#[sel(windowWillEnterFullScreen:)]
fn window_will_enter_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowWillEnterFullScreen:");
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 {
Some(Fullscreen::Exclusive(_)) => (),
Some(Fullscreen::Borderless(_)) => (),
None => {
let current_monitor = self.window.current_monitor_inner();
shared_state.fullscreen = Some(Fullscreen::Borderless(current_monitor))
}
}
shared_state.in_fullscreen_transition = true;
}
#[sel(windowWillExitFullScreen:)]
fn window_will_exit_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowWillExitFullScreen:");
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,
_: Option<&Object>,
proposed_options: NSApplicationPresentationOptions,
) -> NSApplicationPresentationOptions {
trace_scope!("window:willUseFullScreenPresentationOptions:");
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
}
#[sel(windowDidEnterFullScreen:)]
fn window_did_enter_fullscreen(&mut self, _: Option<&Object>) {
trace_scope!("windowDidEnterFullScreen:");
*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);
}
}
#[sel(windowDidExitFullScreen:)]
fn window_did_exit_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowDidExitFullScreen:");
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);
}
}
#[sel(windowDidFailToEnterFullScreen:)]
fn window_did_fail_to_enter_fullscreen(&self, _: Option<&Object>) {
trace_scope!("windowDidFailToEnterFullScreen:");
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();
}
}
#[sel(windowDidChangeOcclusionState:)]
fn window_did_change_occlusion_state(&self, _: Option<&Object>) {
trace_scope!("windowDidChangeOcclusionState:");
self.queue_event(WindowEvent::Occluded(
!self
.window
.occlusionState()
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
))
}
#[sel(effectiveAppearanceDidChange:)]
fn effective_appearance_did_change(&self, sender: Option<&Object>) {
trace_scope!("Triggered `effectiveAppearanceDidChange:`");
unsafe {
msg_send![
self,
performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:),
withObject: sender,
waitUntilDone: false,
]
}
}
#[sel(effectiveAppearanceDidChangedOnMainThread:)]
fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&Object>) {
let theme = get_ns_theme();
let mut shared_state = self
.window
.lock_shared_state("effective_appearance_did_change");
let current_theme = shared_state.current_theme;
shared_state.current_theme = Some(theme);
drop(shared_state);
if current_theme != Some(theme) {
self.queue_event(WindowEvent::ThemeChanged(theme));
}
}
#[sel(windowDidChangeScreen:)]
fn window_did_change_screen(&self, _: Option<&Object>) {
trace_scope!("windowDidChangeScreen:");
let is_simple_fullscreen = self
.window
.lock_shared_state("window_did_change_screen")
.is_simple_fullscreen;
if is_simple_fullscreen {
if let Some(screen) = self.window.screen() {
self.window.setFrame_display(screen.frame(), true);
}
}
}
}
);
impl WinitWindowDelegate {
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,
]
}
}
pub(crate) fn queue_event(&self, event: WindowEvent<'static>) {
let event = Event::WindowEvent {
window_id: WindowId(self.window.id()),
event,
};
AppState::queue_event(EventWrapper::StaticEvent(event));
}
fn queue_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.queue_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)
}
}