mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
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
|
@ -65,11 +65,8 @@ ndk-glue = "0.7.0"
|
|||
objc = { version = "=0.3.0-beta.3", package = "objc2" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
# Branch: objc2
|
||||
# TODO: Use non-git versions before we release
|
||||
cocoa = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
|
||||
core-foundation = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
|
||||
core-graphics = { git = "https://github.com/madsmtm/core-foundation-rs.git", rev = "c770e620ba0766fc1d2a9f83327b0fee905eb5cb" }
|
||||
core-foundation = "0.9.3"
|
||||
core-graphics = "0.22.3"
|
||||
dispatch = "0.2.0"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
||||
|
|
|
@ -145,7 +145,7 @@ extern crate log;
|
|||
extern crate serde;
|
||||
#[macro_use]
|
||||
extern crate bitflags;
|
||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
||||
#[cfg(target_os = "ios")]
|
||||
#[macro_use]
|
||||
extern crate objc;
|
||||
#[cfg(target_os = "macos")]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use objc2::rc::Id;
|
||||
use std::os::raw::c_void;
|
||||
|
||||
use crate::{
|
||||
|
@ -240,7 +241,7 @@ impl MonitorHandleExtMacOS for MonitorHandle {
|
|||
}
|
||||
|
||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
||||
self.inner.ns_screen().map(|s| Id::as_ptr(&s) as _)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{self, NSEvent},
|
||||
base::id,
|
||||
};
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::{declare_class, ClassType};
|
||||
use objc2::{declare_class, msg_send, ClassType};
|
||||
|
||||
use super::appkit::{NSApplication, NSResponder};
|
||||
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
||||
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
|
||||
use super::{app_state::AppState, event::EventWrapper, DEVICE_ID};
|
||||
use crate::event::{DeviceEvent, ElementState, Event};
|
||||
|
||||
declare_class!(
|
||||
|
@ -25,37 +21,33 @@ declare_class!(
|
|||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
||||
#[sel(sendEvent:)]
|
||||
fn send_event(&self, event: id) {
|
||||
unsafe {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = event.eventType();
|
||||
let modifier_flags = event.modifierFlags();
|
||||
if event_type == appkit::NSKeyUp
|
||||
&& util::has_flag(
|
||||
modifier_flags,
|
||||
appkit::NSEventModifierFlags::NSCommandKeyMask,
|
||||
)
|
||||
{
|
||||
let key_window: id = msg_send![self, keyWindow];
|
||||
let _: () = msg_send![key_window, sendEvent: event];
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
let _: () = msg_send![super(self), sendEvent: event];
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// For posterity, there are some undocumented event types
|
||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
||||
// but that doesn't really matter here.
|
||||
let event_type = event.type_();
|
||||
let modifier_flags = event.modifierFlags();
|
||||
if event_type == NSEventType::NSKeyUp
|
||||
&& modifier_flags.contains(NSEventModifierFlags::NSCommandKeyMask)
|
||||
{
|
||||
if let Some(key_window) = self.keyWindow() {
|
||||
unsafe { key_window.sendEvent(event) };
|
||||
}
|
||||
} else {
|
||||
maybe_dispatch_device_event(event);
|
||||
unsafe { msg_send![super(self), sendEvent: event] }
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe fn maybe_dispatch_device_event(event: id) {
|
||||
let event_type = event.eventType();
|
||||
fn maybe_dispatch_device_event(event: &NSEvent) {
|
||||
let event_type = event.type_();
|
||||
match event_type {
|
||||
appkit::NSMouseMoved
|
||||
| appkit::NSLeftMouseDragged
|
||||
| appkit::NSOtherMouseDragged
|
||||
| appkit::NSRightMouseDragged => {
|
||||
NSEventType::NSMouseMoved
|
||||
| NSEventType::NSLeftMouseDragged
|
||||
| NSEventType::NSOtherMouseDragged
|
||||
| NSEventType::NSRightMouseDragged => {
|
||||
let mut events = VecDeque::with_capacity(3);
|
||||
|
||||
let delta_x = event.deltaX() as f64;
|
||||
|
@ -92,7 +84,9 @@ unsafe fn maybe_dispatch_device_event(event: id) {
|
|||
|
||||
AppState::queue_events(events);
|
||||
}
|
||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
||||
NSEventType::NSLeftMouseDown
|
||||
| NSEventType::NSRightMouseDown
|
||||
| NSEventType::NSOtherMouseDown => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
||||
|
@ -105,7 +99,7 @@ unsafe fn maybe_dispatch_device_event(event: id) {
|
|||
|
||||
AppState::queue_events(events);
|
||||
}
|
||||
appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => {
|
||||
NSEventType::NSLeftMouseUp | NSEventType::NSRightMouseUp | NSEventType::NSOtherMouseUp => {
|
||||
let mut events = VecDeque::with_capacity(1);
|
||||
|
||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use cocoa::appkit::NSApplicationActivationPolicy;
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{declare_class, ClassType};
|
||||
use objc2::{declare_class, msg_send, msg_send_id, ClassType};
|
||||
|
||||
use super::app_state::AppState;
|
||||
use super::appkit::NSApplicationActivationPolicy;
|
||||
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::{
|
|||
cell::{RefCell, RefMut},
|
||||
collections::VecDeque,
|
||||
fmt::{self, Debug},
|
||||
hint::unreachable_unchecked,
|
||||
mem,
|
||||
rc::{Rc, Weak},
|
||||
sync::{
|
||||
|
@ -12,27 +11,22 @@ use std::{
|
|||
time::Instant,
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSWindow},
|
||||
base::{id, nil},
|
||||
foundation::NSSize,
|
||||
};
|
||||
use objc::foundation::is_main_thread;
|
||||
use objc::rc::autoreleasepool;
|
||||
use objc::runtime::Bool;
|
||||
use objc2::foundation::{is_main_thread, NSSize};
|
||||
use objc2::rc::autoreleasepool;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
||||
platform_impl::platform::{
|
||||
event::{EventProxy, EventWrapper},
|
||||
event_loop::{post_dummy_event, PanicInfo},
|
||||
event_loop::PanicInfo,
|
||||
menu,
|
||||
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
|
||||
util::{IdRef, Never},
|
||||
window::get_window_id,
|
||||
util::Never,
|
||||
window::WinitWindow,
|
||||
},
|
||||
window::WindowId,
|
||||
};
|
||||
|
@ -44,7 +38,7 @@ impl<'a, Never> Event<'a, Never> {
|
|||
self.map_nonuser_event()
|
||||
// `Never` can't be constructed, so the `UserEvent` variant can't
|
||||
// be present here.
|
||||
.unwrap_or_else(|_| unsafe { unreachable_unchecked() })
|
||||
.unwrap_or_else(|_| unreachable!())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -217,14 +211,14 @@ impl Handler {
|
|||
fn handle_scale_factor_changed_event(
|
||||
&self,
|
||||
callback: &mut Box<dyn EventHandler + 'static>,
|
||||
ns_window: IdRef,
|
||||
window: &WinitWindow,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
) {
|
||||
let mut size = suggested_size.to_physical(scale_factor);
|
||||
let new_inner_size = &mut size;
|
||||
let event = Event::WindowEvent {
|
||||
window_id: WindowId(get_window_id(*ns_window)),
|
||||
window_id: WindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
|
@ -236,18 +230,18 @@ impl Handler {
|
|||
let physical_size = *new_inner_size;
|
||||
let logical_size = physical_size.to_logical(scale_factor);
|
||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
||||
unsafe { NSWindow::setContentSize_(*ns_window, size) };
|
||||
window.setContentSize(size);
|
||||
}
|
||||
|
||||
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
|
||||
match proxy {
|
||||
EventProxy::DpiChangedProxy {
|
||||
ns_window,
|
||||
window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
} => self.handle_scale_factor_changed_event(
|
||||
callback,
|
||||
ns_window,
|
||||
&window,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
),
|
||||
|
@ -255,7 +249,7 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
pub enum AppState {}
|
||||
pub(crate) enum AppState {}
|
||||
|
||||
impl AppState {
|
||||
pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) {
|
||||
|
@ -278,18 +272,16 @@ impl AppState {
|
|||
}
|
||||
|
||||
pub fn launched(activation_policy: NSApplicationActivationPolicy, create_default_menu: bool) {
|
||||
unsafe {
|
||||
let ns_app = NSApp();
|
||||
let app = NSApp();
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
app.setActivationPolicy(activation_policy);
|
||||
|
||||
// We need to delay setting the activation policy and activating the app
|
||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
||||
// menu bar is initially unresponsive on macOS 10.15.
|
||||
ns_app.setActivationPolicy_(activation_policy);
|
||||
window_activation_hack(&app);
|
||||
// TODO: Consider allowing the user to specify they don't want their application activated
|
||||
app.activateIgnoringOtherApps(true);
|
||||
|
||||
window_activation_hack(ns_app);
|
||||
// TODO: Consider allowing the user to specify they don't want their application activated
|
||||
ns_app.activateIgnoringOtherApps_(Bool::YES.as_raw());
|
||||
};
|
||||
HANDLER.set_ready();
|
||||
HANDLER.waker().start();
|
||||
if create_default_menu {
|
||||
|
@ -397,15 +389,12 @@ impl AppState {
|
|||
HANDLER.set_in_callback(false);
|
||||
|
||||
if HANDLER.should_exit() {
|
||||
unsafe {
|
||||
let app: id = NSApp();
|
||||
|
||||
autoreleasepool(|_| {
|
||||
let _: () = msg_send![app, stop: nil];
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
post_dummy_event(app);
|
||||
});
|
||||
};
|
||||
let app = NSApp();
|
||||
autoreleasepool(|_| {
|
||||
app.stop(None);
|
||||
// To stop event loop immediately, we need to post some event here.
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
});
|
||||
}
|
||||
HANDLER.update_start_time();
|
||||
match HANDLER.get_old_and_new_control_flow() {
|
||||
|
@ -426,25 +415,17 @@ impl AppState {
|
|||
///
|
||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
||||
/// without too much damage.
|
||||
unsafe fn window_activation_hack(ns_app: id) {
|
||||
// Get the application's windows
|
||||
fn window_activation_hack(app: &NSApplication) {
|
||||
// TODO: Proper ordering of the windows
|
||||
let ns_windows: id = msg_send![ns_app, windows];
|
||||
let ns_enumerator: id = msg_send![ns_windows, objectEnumerator];
|
||||
loop {
|
||||
// Enumerate over the windows
|
||||
let ns_window: id = msg_send![ns_enumerator, nextObject];
|
||||
if ns_window == nil {
|
||||
break;
|
||||
}
|
||||
// And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new`
|
||||
app.windows().into_iter().for_each(|window| {
|
||||
// Call `makeKeyAndOrderFront` if it was called on the window in `WinitWindow::new`
|
||||
// This way we preserve the user's desired initial visiblity status
|
||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
||||
if Bool::from_raw(ns_window.isVisible()).as_bool() {
|
||||
if window.isVisible() {
|
||||
trace!("Activating visible window");
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
window.makeKeyAndOrderFront(None);
|
||||
} else {
|
||||
trace!("Skipping activating invisible window");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use objc2::foundation::{MainThreadMarker, NSArray, NSInteger, NSObject, NSUInteger};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
use objc2::{Encode, Encoding};
|
||||
|
||||
use super::NSResponder;
|
||||
use super::{NSEvent, NSMenu, NSResponder, NSWindow};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -12,3 +15,121 @@ extern_class!(
|
|||
type Super = NSResponder;
|
||||
}
|
||||
);
|
||||
|
||||
pub(crate) fn NSApp() -> Id<NSApplication, Shared> {
|
||||
// TODO: Only allow access from main thread
|
||||
NSApplication::shared(unsafe { MainThreadMarker::new_unchecked() })
|
||||
}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSApplication {
|
||||
/// This can only be called on the main thread since it may initialize
|
||||
/// the application and since it's parameters may be changed by the main
|
||||
/// thread at any time (hence it is only safe to access on the main thread).
|
||||
pub fn shared(_mtm: MainThreadMarker) -> Id<Self, Shared> {
|
||||
let app: Option<_> = unsafe { msg_send_id![Self::class(), sharedApplication] };
|
||||
// SAFETY: `sharedApplication` always initializes the app if it isn't already
|
||||
unsafe { app.unwrap_unchecked() }
|
||||
}
|
||||
|
||||
pub fn currentEvent(&self) -> Option<Id<NSEvent, Shared>> {
|
||||
unsafe { msg_send_id![self, currentEvent] }
|
||||
}
|
||||
|
||||
#[sel(postEvent:atStart:)]
|
||||
pub fn postEvent_atStart(&self, event: &NSEvent, front_of_queue: bool);
|
||||
|
||||
#[sel(presentationOptions)]
|
||||
pub fn presentationOptions(&self) -> NSApplicationPresentationOptions;
|
||||
|
||||
pub fn windows(&self) -> Id<NSArray<NSWindow, Shared>, Shared> {
|
||||
unsafe { msg_send_id![self, windows] }
|
||||
}
|
||||
|
||||
pub fn keyWindow(&self) -> Option<Id<NSWindow, Shared>> {
|
||||
unsafe { msg_send_id![self, keyWindow] }
|
||||
}
|
||||
|
||||
// TODO: NSApplicationDelegate
|
||||
#[sel(setDelegate:)]
|
||||
pub fn setDelegate(&self, delegate: &Object);
|
||||
|
||||
#[sel(setPresentationOptions:)]
|
||||
pub fn setPresentationOptions(&self, options: NSApplicationPresentationOptions);
|
||||
|
||||
#[sel(hide:)]
|
||||
pub fn hide(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(orderFrontCharacterPalette:)]
|
||||
#[allow(dead_code)]
|
||||
pub fn orderFrontCharacterPalette(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(hideOtherApplications:)]
|
||||
pub fn hideOtherApplications(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(stop:)]
|
||||
pub fn stop(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(activateIgnoringOtherApps:)]
|
||||
pub fn activateIgnoringOtherApps(&self, ignore: bool);
|
||||
|
||||
#[sel(requestUserAttention:)]
|
||||
pub fn requestUserAttention(&self, type_: NSRequestUserAttentionType) -> NSInteger;
|
||||
|
||||
#[sel(setActivationPolicy:)]
|
||||
pub fn setActivationPolicy(&self, policy: NSApplicationActivationPolicy) -> bool;
|
||||
|
||||
#[sel(setMainMenu:)]
|
||||
pub fn setMainMenu(&self, menu: &NSMenu);
|
||||
|
||||
#[sel(run)]
|
||||
pub unsafe fn run(&self);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSApplicationActivationPolicy {
|
||||
NSApplicationActivationPolicyRegular = 0,
|
||||
NSApplicationActivationPolicyAccessory = 1,
|
||||
NSApplicationActivationPolicyProhibited = 2,
|
||||
NSApplicationActivationPolicyERROR = -1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationActivationPolicy {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct NSApplicationPresentationOptions: NSUInteger {
|
||||
const NSApplicationPresentationDefault = 0;
|
||||
const NSApplicationPresentationAutoHideDock = 1 << 0;
|
||||
const NSApplicationPresentationHideDock = 1 << 1;
|
||||
const NSApplicationPresentationAutoHideMenuBar = 1 << 2;
|
||||
const NSApplicationPresentationHideMenuBar = 1 << 3;
|
||||
const NSApplicationPresentationDisableAppleMenu = 1 << 4;
|
||||
const NSApplicationPresentationDisableProcessSwitching = 1 << 5;
|
||||
const NSApplicationPresentationDisableForceQuit = 1 << 6;
|
||||
const NSApplicationPresentationDisableSessionTermination = 1 << 7;
|
||||
const NSApplicationPresentationDisableHideApplication = 1 << 8;
|
||||
const NSApplicationPresentationDisableMenuBarTransparency = 1 << 9;
|
||||
const NSApplicationPresentationFullScreen = 1 << 10;
|
||||
const NSApplicationPresentationAutoHideToolbar = 1 << 11;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSApplicationPresentationOptions {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSRequestUserAttentionType {
|
||||
NSCriticalRequest = 0,
|
||||
NSInformationalRequest = 10,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSRequestUserAttentionType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
|
14
src/platform_impl/macos/appkit/button.rs
Normal file
14
src/platform_impl/macos/appkit/button.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
|
||||
use super::{NSControl, NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSButton;
|
||||
|
||||
unsafe impl ClassType for NSButton {
|
||||
#[inherits(NSView, NSResponder, NSObject)]
|
||||
type Super = NSControl;
|
||||
}
|
||||
);
|
28
src/platform_impl/macos/appkit/color.rs
Normal file
28
src/platform_impl/macos/appkit/color.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
/// An object that stores color data and sometimes opacity (alpha value).
|
||||
///
|
||||
/// <https://developer.apple.com/documentation/appkit/nscolor?language=objc>
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSColor;
|
||||
|
||||
unsafe impl ClassType for NSColor {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// SAFETY: Documentation clearly states:
|
||||
// > Color objects are immutable and thread-safe
|
||||
unsafe impl Send for NSColor {}
|
||||
unsafe impl Sync for NSColor {}
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSColor {
|
||||
pub fn clear() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), clearColor] }
|
||||
}
|
||||
}
|
||||
);
|
14
src/platform_impl/macos/appkit/control.rs
Normal file
14
src/platform_impl/macos/appkit/control.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
|
||||
use super::{NSResponder, NSView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSControl;
|
||||
|
||||
unsafe impl ClassType for NSControl {
|
||||
#[inherits(NSResponder, NSObject)]
|
||||
type Super = NSView;
|
||||
}
|
||||
);
|
|
@ -3,7 +3,7 @@ use once_cell::sync::Lazy;
|
|||
use objc2::foundation::{NSData, NSDictionary, NSNumber, NSObject, NSPoint, NSString};
|
||||
use objc2::rc::{DefaultId, Id, Shared};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ns_string, sel, ClassType};
|
||||
|
||||
use super::NSImage;
|
||||
use crate::window::CursorIcon;
|
||||
|
|
234
src/platform_impl/macos/appkit/event.rs
Normal file
234
src/platform_impl/macos/appkit/event.rs
Normal file
|
@ -0,0 +1,234 @@
|
|||
use std::os::raw::c_ushort;
|
||||
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{
|
||||
CGFloat, NSCopying, NSInteger, NSObject, NSPoint, NSString, NSTimeInterval, NSUInteger,
|
||||
};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSEvent;
|
||||
|
||||
unsafe impl ClassType for NSEvent {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// > Safely handled only on the same thread, whether that be the main thread
|
||||
// > or a secondary thread; otherwise you run the risk of having events get
|
||||
// > out of sequence.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123383>
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSEvent {
|
||||
unsafe fn otherEventWithType(
|
||||
type_: NSEventType,
|
||||
location: NSPoint,
|
||||
flags: NSEventModifierFlags,
|
||||
time: NSTimeInterval,
|
||||
window_num: NSInteger,
|
||||
context: Option<&NSObject>, // NSGraphicsContext
|
||||
subtype: NSEventSubtype,
|
||||
data1: NSInteger,
|
||||
data2: NSInteger,
|
||||
) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
Self::class(),
|
||||
otherEventWithType: type_,
|
||||
location: location,
|
||||
modifierFlags: flags,
|
||||
timestamp: time,
|
||||
windowNumber: window_num,
|
||||
context: context,
|
||||
subtype: subtype,
|
||||
data1: data1,
|
||||
data2: data2,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dummy() -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
Self::otherEventWithType(
|
||||
NSEventType::NSApplicationDefined,
|
||||
NSPoint::new(0.0, 0.0),
|
||||
NSEventModifierFlags::empty(),
|
||||
0.0,
|
||||
0,
|
||||
None,
|
||||
NSEventSubtype::NSWindowExposedEventType,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[sel(locationInWindow)]
|
||||
pub fn locationInWindow(&self) -> NSPoint;
|
||||
|
||||
// TODO: MainThreadMarker
|
||||
#[sel(pressedMouseButtons)]
|
||||
pub fn pressedMouseButtons() -> NSUInteger;
|
||||
|
||||
#[sel(modifierFlags)]
|
||||
pub fn modifierFlags(&self) -> NSEventModifierFlags;
|
||||
|
||||
#[sel(type)]
|
||||
pub fn type_(&self) -> NSEventType;
|
||||
|
||||
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
|
||||
// and there is no easy way to navtively retrieve the layout-dependent character.
|
||||
// In winit, we use keycode to refer to the key's character, and so this function aligns
|
||||
// AppKit's terminology with ours.
|
||||
#[sel(keyCode)]
|
||||
pub fn scancode(&self) -> c_ushort;
|
||||
|
||||
#[sel(magnification)]
|
||||
pub fn magnification(&self) -> CGFloat;
|
||||
|
||||
#[sel(phase)]
|
||||
pub fn phase(&self) -> NSEventPhase;
|
||||
|
||||
#[sel(momentumPhase)]
|
||||
pub fn momentumPhase(&self) -> NSEventPhase;
|
||||
|
||||
#[sel(deltaX)]
|
||||
pub fn deltaX(&self) -> CGFloat;
|
||||
|
||||
#[sel(deltaY)]
|
||||
pub fn deltaY(&self) -> CGFloat;
|
||||
|
||||
#[sel(buttonNumber)]
|
||||
pub fn buttonNumber(&self) -> NSInteger;
|
||||
|
||||
#[sel(scrollingDeltaX)]
|
||||
pub fn scrollingDeltaX(&self) -> CGFloat;
|
||||
|
||||
#[sel(scrollingDeltaY)]
|
||||
pub fn scrollingDeltaY(&self) -> CGFloat;
|
||||
|
||||
#[sel(hasPreciseScrollingDeltas)]
|
||||
pub fn hasPreciseScrollingDeltas(&self) -> bool;
|
||||
|
||||
#[sel(rotation)]
|
||||
pub fn rotation(&self) -> f32;
|
||||
|
||||
#[sel(pressure)]
|
||||
pub fn pressure(&self) -> f32;
|
||||
|
||||
#[sel(stage)]
|
||||
pub fn stage(&self) -> NSInteger;
|
||||
|
||||
pub fn characters(&self) -> Option<Id<NSString, Shared>> {
|
||||
unsafe { msg_send_id![self, characters] }
|
||||
}
|
||||
|
||||
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString, Shared>> {
|
||||
unsafe { msg_send_id![self, charactersIgnoringModifiers] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
unsafe impl NSCopying for NSEvent {
|
||||
type Ownership = Shared;
|
||||
type Output = NSEvent;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct NSEventModifierFlags: NSUInteger {
|
||||
const NSAlphaShiftKeyMask = 1 << 16;
|
||||
const NSShiftKeyMask = 1 << 17;
|
||||
const NSControlKeyMask = 1 << 18;
|
||||
const NSAlternateKeyMask = 1 << 19;
|
||||
const NSCommandKeyMask = 1 << 20;
|
||||
const NSNumericPadKeyMask = 1 << 21;
|
||||
const NSHelpKeyMask = 1 << 22;
|
||||
const NSFunctionKeyMask = 1 << 23;
|
||||
const NSDeviceIndependentModifierFlagsMask = 0xffff0000;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventModifierFlags {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct NSEventPhase: NSUInteger {
|
||||
const NSEventPhaseNone = 0;
|
||||
const NSEventPhaseBegan = 0x1 << 0;
|
||||
const NSEventPhaseStationary = 0x1 << 1;
|
||||
const NSEventPhaseChanged = 0x1 << 2;
|
||||
const NSEventPhaseEnded = 0x1 << 3;
|
||||
const NSEventPhaseCancelled = 0x1 << 4;
|
||||
const NSEventPhaseMayBegin = 0x1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventPhase {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(i16)] // short
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSEventSubtype {
|
||||
// TODO: Not sure what these values are
|
||||
// NSMouseEventSubtype = NX_SUBTYPE_DEFAULT,
|
||||
// NSTabletPointEventSubtype = NX_SUBTYPE_TABLET_POINT,
|
||||
// NSTabletProximityEventSubtype = NX_SUBTYPE_TABLET_PROXIMITY
|
||||
// NSTouchEventSubtype = NX_SUBTYPE_MOUSE_TOUCH,
|
||||
NSWindowExposedEventType = 0,
|
||||
NSApplicationActivatedEventType = 1,
|
||||
NSApplicationDeactivatedEventType = 2,
|
||||
NSWindowMovedEventType = 4,
|
||||
NSScreenChangedEventType = 8,
|
||||
NSAWTEventType = 16,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventSubtype {
|
||||
const ENCODING: Encoding = i16::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
pub enum NSEventType {
|
||||
NSLeftMouseDown = 1,
|
||||
NSLeftMouseUp = 2,
|
||||
NSRightMouseDown = 3,
|
||||
NSRightMouseUp = 4,
|
||||
NSMouseMoved = 5,
|
||||
NSLeftMouseDragged = 6,
|
||||
NSRightMouseDragged = 7,
|
||||
NSMouseEntered = 8,
|
||||
NSMouseExited = 9,
|
||||
NSKeyDown = 10,
|
||||
NSKeyUp = 11,
|
||||
NSFlagsChanged = 12,
|
||||
NSAppKitDefined = 13,
|
||||
NSSystemDefined = 14,
|
||||
NSApplicationDefined = 15,
|
||||
NSPeriodic = 16,
|
||||
NSCursorUpdate = 17,
|
||||
NSScrollWheel = 22,
|
||||
NSTabletPoint = 23,
|
||||
NSTabletProximity = 24,
|
||||
NSOtherMouseDown = 25,
|
||||
NSOtherMouseUp = 26,
|
||||
NSOtherMouseDragged = 27,
|
||||
NSEventTypeGesture = 29,
|
||||
NSEventTypeMagnify = 30,
|
||||
NSEventTypeSwipe = 31,
|
||||
NSEventTypeRotate = 18,
|
||||
NSEventTypeBeginGesture = 19,
|
||||
NSEventTypeEndGesture = 20,
|
||||
NSEventTypePressure = 34,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSEventType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
25
src/platform_impl/macos/appkit/menu.rs
Normal file
25
src/platform_impl/macos/appkit/menu.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
use super::NSMenuItem;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenu;
|
||||
|
||||
unsafe impl ClassType for NSMenu {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenu {
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
#[sel(addItem:)]
|
||||
pub fn addItem(&self, item: &NSMenuItem);
|
||||
}
|
||||
);
|
48
src/platform_impl/macos/appkit/menu_item.rs
Normal file
48
src/platform_impl/macos/appkit/menu_item.rs
Normal file
|
@ -0,0 +1,48 @@
|
|||
use objc2::foundation::{NSObject, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
use super::{NSEventModifierFlags, NSMenu};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSMenuItem;
|
||||
|
||||
unsafe impl ClassType for NSMenuItem {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSMenuItem {
|
||||
pub fn new() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), new] }
|
||||
}
|
||||
|
||||
pub fn newWithTitle(
|
||||
title: &NSString,
|
||||
action: Sel,
|
||||
key_equivalent: &NSString,
|
||||
) -> Id<Self, Shared> {
|
||||
unsafe {
|
||||
msg_send_id![
|
||||
msg_send_id![Self::class(), alloc],
|
||||
initWithTitle: title,
|
||||
action: action,
|
||||
keyEquivalent: key_equivalent,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separatorItem() -> Id<Self, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), separatorItem] }
|
||||
}
|
||||
|
||||
#[sel(setKeyEquivalentModifierMask:)]
|
||||
pub fn setKeyEquivalentModifierMask(&self, mask: NSEventModifierFlags);
|
||||
|
||||
#[sel(setSubmenu:)]
|
||||
pub fn setSubmenu(&self, submenu: &NSMenu);
|
||||
}
|
||||
);
|
|
@ -1,18 +1,59 @@
|
|||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
//! Safe bindings for the AppKit framework.
|
||||
//!
|
||||
//! These are split out from the rest of `winit` to make safety easier to review.
|
||||
//! In the future, these should probably live in another crate like `cacao`.
|
||||
//!
|
||||
//! TODO: Main thread safety.
|
||||
// Objective-C methods have different conventions, and it's much easier to
|
||||
// understand if we just use the same names
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
#![allow(clippy::enum_variant_names)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
|
||||
mod application;
|
||||
mod button;
|
||||
mod color;
|
||||
mod control;
|
||||
mod cursor;
|
||||
mod event;
|
||||
mod image;
|
||||
mod menu;
|
||||
mod menu_item;
|
||||
mod pasteboard;
|
||||
mod responder;
|
||||
mod screen;
|
||||
mod text_input_context;
|
||||
mod version;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub(crate) use self::application::NSApplication;
|
||||
pub(crate) use self::application::{
|
||||
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
||||
NSRequestUserAttentionType,
|
||||
};
|
||||
pub(crate) use self::button::NSButton;
|
||||
pub(crate) use self::color::NSColor;
|
||||
pub(crate) use self::control::NSControl;
|
||||
pub(crate) use self::cursor::NSCursor;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::event::{
|
||||
NSEvent, NSEventModifierFlags, NSEventPhase, NSEventSubtype, NSEventType,
|
||||
};
|
||||
pub(crate) use self::image::NSImage;
|
||||
pub(crate) use self::menu::NSMenu;
|
||||
pub(crate) use self::menu_item::NSMenuItem;
|
||||
pub(crate) use self::pasteboard::{NSFilenamesPboardType, NSPasteboard, NSPasteboardType};
|
||||
pub(crate) use self::responder::NSResponder;
|
||||
pub(crate) use self::view::NSView;
|
||||
pub(crate) use self::window::NSWindow;
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use self::screen::{NSDeviceDescriptionKey, NSScreen};
|
||||
pub(crate) use self::text_input_context::NSTextInputContext;
|
||||
pub(crate) use self::version::NSAppKitVersion;
|
||||
pub(crate) use self::view::{NSTrackingRectTag, NSView};
|
||||
pub(crate) use self::window::{
|
||||
NSBackingStoreType, NSWindow, NSWindowButton, NSWindowLevel, NSWindowOcclusionState,
|
||||
NSWindowStyleMask, NSWindowTitleVisibility,
|
||||
};
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {}
|
||||
|
|
26
src/platform_impl/macos/appkit/pasteboard.rs
Normal file
26
src/platform_impl/macos/appkit/pasteboard.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use objc2::foundation::{NSObject, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSPasteboard;
|
||||
|
||||
unsafe impl ClassType for NSPasteboard {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSPasteboard {
|
||||
pub fn propertyListForType(&self, type_: &NSPasteboardType) -> Id<NSObject, Shared> {
|
||||
unsafe { msg_send_id![self, propertyListForType: type_] }
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSPasteboardType = NSString;
|
||||
|
||||
extern "C" {
|
||||
pub static NSFilenamesPboardType: &'static NSPasteboardType;
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use objc2::foundation::{NSArray, NSObject};
|
||||
use objc2::rc::Shared;
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
|
||||
use super::NSEvent;
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -9,3 +12,13 @@ extern_class!(
|
|||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Thread-Unsafe".
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSResponder {
|
||||
// TODO: Allow "immutably" on main thread
|
||||
#[sel(interpretKeyEvents:)]
|
||||
pub unsafe fn interpretKeyEvents(&mut self, events: &NSArray<NSEvent, Shared>);
|
||||
}
|
||||
);
|
||||
|
|
64
src/platform_impl/macos/appkit/screen.rs
Normal file
64
src/platform_impl/macos/appkit/screen.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use objc2::foundation::{CGFloat, NSArray, NSDictionary, NSNumber, NSObject, NSRect, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ns_string, ClassType};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSScreen;
|
||||
|
||||
unsafe impl ClassType for NSScreen {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: Main thread marker!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSScreen {
|
||||
/// The application object must have been created.
|
||||
pub fn main() -> Option<Id<Self, Shared>> {
|
||||
unsafe { msg_send_id![Self::class(), mainScreen] }
|
||||
}
|
||||
|
||||
/// The application object must have been created.
|
||||
pub fn screens() -> Id<NSArray<Self, Shared>, Shared> {
|
||||
unsafe { msg_send_id![Self::class(), screens] }
|
||||
}
|
||||
|
||||
#[sel(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[sel(visibleFrame)]
|
||||
pub fn visibleFrame(&self) -> NSRect;
|
||||
|
||||
pub fn deviceDescription(
|
||||
&self,
|
||||
) -> Id<NSDictionary<NSDeviceDescriptionKey, Object>, Shared> {
|
||||
unsafe { msg_send_id![self, deviceDescription] }
|
||||
}
|
||||
|
||||
pub fn display_id(&self) -> u32 {
|
||||
let device_description = self.deviceDescription();
|
||||
|
||||
// Retrieve the CGDirectDisplayID associated with this screen
|
||||
//
|
||||
// SAFETY: The value from @"NSScreenNumber" in deviceDescription is guaranteed
|
||||
// to be an NSNumber. See documentation for `deviceDescription` for details:
|
||||
// <https://developer.apple.com/documentation/appkit/nsscreen/1388360-devicedescription?language=objc>
|
||||
let obj = device_description
|
||||
.get(ns_string!("NSScreenNumber"))
|
||||
.expect("failed getting screen display id from device description");
|
||||
let obj: *const Object = obj;
|
||||
let obj: *const NSNumber = obj.cast();
|
||||
let obj: &NSNumber = unsafe { &*obj };
|
||||
|
||||
obj.as_u32()
|
||||
}
|
||||
|
||||
#[sel(backingScaleFactor)]
|
||||
pub fn backingScaleFactor(&self) -> CGFloat;
|
||||
}
|
||||
);
|
||||
|
||||
pub type NSDeviceDescriptionKey = NSString;
|
31
src/platform_impl/macos/appkit/text_input_context.rs
Normal file
31
src/platform_impl/macos/appkit/text_input_context.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use objc2::foundation::{NSObject, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
type NSTextInputSourceIdentifier = NSString;
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSTextInputContext;
|
||||
|
||||
unsafe impl ClassType for NSTextInputContext {
|
||||
type Super = NSObject;
|
||||
}
|
||||
);
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSTextInputContext {
|
||||
#[sel(invalidateCharacterCoordinates)]
|
||||
pub fn invalidateCharacterCoordinates(&self);
|
||||
|
||||
#[sel(discardMarkedText)]
|
||||
pub fn discardMarkedText(&self);
|
||||
|
||||
pub fn selectedKeyboardInputSource(
|
||||
&self,
|
||||
) -> Option<Id<NSTextInputSourceIdentifier, Shared>> {
|
||||
unsafe { msg_send_id![self, selectedKeyboardInputSource] }
|
||||
}
|
||||
}
|
||||
);
|
62
src/platform_impl/macos/appkit/version.rs
Normal file
62
src/platform_impl/macos/appkit/version.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
#[repr(transparent)]
|
||||
#[derive(PartialEq, PartialOrd, Debug, Clone, Copy)]
|
||||
pub struct NSAppKitVersion(f64);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
impl NSAppKitVersion {
|
||||
pub fn current() -> Self {
|
||||
extern "C" {
|
||||
static NSAppKitVersionNumber: NSAppKitVersion;
|
||||
}
|
||||
|
||||
unsafe { NSAppKitVersionNumber }
|
||||
}
|
||||
|
||||
pub fn floor(self) -> Self {
|
||||
Self(self.0.floor())
|
||||
}
|
||||
|
||||
pub const NSAppKitVersionNumber10_0: Self = Self(577.0);
|
||||
pub const NSAppKitVersionNumber10_1: Self = Self(620.0);
|
||||
pub const NSAppKitVersionNumber10_2: Self = Self(663.0);
|
||||
pub const NSAppKitVersionNumber10_2_3: Self = Self(663.6);
|
||||
pub const NSAppKitVersionNumber10_3: Self = Self(743.0);
|
||||
pub const NSAppKitVersionNumber10_3_2: Self = Self(743.14);
|
||||
pub const NSAppKitVersionNumber10_3_3: Self = Self(743.2);
|
||||
pub const NSAppKitVersionNumber10_3_5: Self = Self(743.24);
|
||||
pub const NSAppKitVersionNumber10_3_7: Self = Self(743.33);
|
||||
pub const NSAppKitVersionNumber10_3_9: Self = Self(743.36);
|
||||
pub const NSAppKitVersionNumber10_4: Self = Self(824.0);
|
||||
pub const NSAppKitVersionNumber10_4_1: Self = Self(824.1);
|
||||
pub const NSAppKitVersionNumber10_4_3: Self = Self(824.23);
|
||||
pub const NSAppKitVersionNumber10_4_4: Self = Self(824.33);
|
||||
pub const NSAppKitVersionNumber10_4_7: Self = Self(824.41);
|
||||
pub const NSAppKitVersionNumber10_5: Self = Self(949.0);
|
||||
pub const NSAppKitVersionNumber10_5_2: Self = Self(949.27);
|
||||
pub const NSAppKitVersionNumber10_5_3: Self = Self(949.33);
|
||||
pub const NSAppKitVersionNumber10_6: Self = Self(1038.0);
|
||||
pub const NSAppKitVersionNumber10_7: Self = Self(1138.0);
|
||||
pub const NSAppKitVersionNumber10_7_2: Self = Self(1138.23);
|
||||
pub const NSAppKitVersionNumber10_7_3: Self = Self(1138.32);
|
||||
pub const NSAppKitVersionNumber10_7_4: Self = Self(1138.47);
|
||||
pub const NSAppKitVersionNumber10_8: Self = Self(1187.0);
|
||||
pub const NSAppKitVersionNumber10_9: Self = Self(1265.0);
|
||||
pub const NSAppKitVersionNumber10_10: Self = Self(1343.0);
|
||||
pub const NSAppKitVersionNumber10_10_2: Self = Self(1344.0);
|
||||
pub const NSAppKitVersionNumber10_10_3: Self = Self(1347.0);
|
||||
pub const NSAppKitVersionNumber10_10_4: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_5: Self = Self(1348.0);
|
||||
pub const NSAppKitVersionNumber10_10_Max: Self = Self(1349.0);
|
||||
pub const NSAppKitVersionNumber10_11: Self = Self(1404.0);
|
||||
pub const NSAppKitVersionNumber10_11_1: Self = Self(1404.13);
|
||||
pub const NSAppKitVersionNumber10_11_2: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_11_3: Self = Self(1404.34);
|
||||
pub const NSAppKitVersionNumber10_12: Self = Self(1504.0);
|
||||
pub const NSAppKitVersionNumber10_12_1: Self = Self(1504.60);
|
||||
pub const NSAppKitVersionNumber10_12_2: Self = Self(1504.76);
|
||||
pub const NSAppKitVersionNumber10_13: Self = Self(1561.0);
|
||||
pub const NSAppKitVersionNumber10_13_1: Self = Self(1561.1);
|
||||
pub const NSAppKitVersionNumber10_13_2: Self = Self(1561.2);
|
||||
pub const NSAppKitVersionNumber10_13_4: Self = Self(1561.4);
|
||||
}
|
|
@ -1,7 +1,13 @@
|
|||
use objc2::foundation::{NSObject, NSRect};
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
use std::ffi::c_void;
|
||||
use std::num::NonZeroIsize;
|
||||
use std::ptr;
|
||||
|
||||
use super::{NSCursor, NSResponder};
|
||||
use objc2::foundation::{NSObject, NSPoint, NSRect};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
use super::{NSCursor, NSResponder, NSTextInputContext};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -13,16 +19,77 @@ extern_class!(
|
|||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only".
|
||||
// > generally thread safe, although operations on views such as creating,
|
||||
// > resizing, and moving should happen on the main thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
//
|
||||
// > If you want to use a thread to draw to a view, bracket all drawing code
|
||||
// > between the lockFocusIfCanDraw and unlockFocus methods of NSView.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123351-BBCFIIEB>
|
||||
|
||||
extern_methods!(
|
||||
/// Getter methods
|
||||
unsafe impl NSView {
|
||||
#[sel(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[sel(bounds)]
|
||||
pub fn bounds(&self) -> NSRect;
|
||||
|
||||
pub fn inputContext(
|
||||
&self,
|
||||
// _mtm: MainThreadMarker,
|
||||
) -> Option<Id<NSTextInputContext, Shared>> {
|
||||
unsafe { msg_send_id![self, inputContext] }
|
||||
}
|
||||
|
||||
#[sel(visibleRect)]
|
||||
pub fn visibleRect(&self) -> NSRect;
|
||||
|
||||
#[sel(hasMarkedText)]
|
||||
pub fn hasMarkedText(&self) -> bool;
|
||||
|
||||
#[sel(convertPoint:fromView:)]
|
||||
pub fn convertPoint_fromView(&self, point: NSPoint, view: Option<&NSView>) -> NSPoint;
|
||||
}
|
||||
|
||||
unsafe impl NSView {
|
||||
#[sel(setWantsBestResolutionOpenGLSurface:)]
|
||||
pub fn setWantsBestResolutionOpenGLSurface(&self, value: bool);
|
||||
|
||||
#[sel(setWantsLayer:)]
|
||||
pub fn setWantsLayer(&self, wants_layer: bool);
|
||||
|
||||
#[sel(setPostsFrameChangedNotifications:)]
|
||||
pub fn setPostsFrameChangedNotifications(&mut self, value: bool);
|
||||
|
||||
#[sel(removeTrackingRect:)]
|
||||
pub fn removeTrackingRect(&self, tag: NSTrackingRectTag);
|
||||
|
||||
#[sel(addTrackingRect:owner:userData:assumeInside:)]
|
||||
unsafe fn inner_addTrackingRect(
|
||||
&self,
|
||||
rect: NSRect,
|
||||
owner: &Object,
|
||||
user_data: *mut c_void,
|
||||
assume_inside: bool,
|
||||
) -> Option<NSTrackingRectTag>;
|
||||
|
||||
pub fn add_tracking_rect(&self, rect: NSRect, assume_inside: bool) -> NSTrackingRectTag {
|
||||
// SAFETY: The user data is NULL, so it is valid
|
||||
unsafe { self.inner_addTrackingRect(rect, self, ptr::null_mut(), assume_inside) }
|
||||
.expect("failed creating tracking rect")
|
||||
}
|
||||
|
||||
#[sel(addCursorRect:cursor:)]
|
||||
// NSCursor safe to take by shared reference since it is already immutable
|
||||
pub fn addCursorRect(&self, rect: NSRect, cursor: &NSCursor);
|
||||
|
||||
#[sel(setHidden:)]
|
||||
pub fn setHidden(&self, hidden: bool);
|
||||
}
|
||||
);
|
||||
|
||||
/// <https://developer.apple.com/documentation/appkit/nstrackingrecttag?language=objc>
|
||||
pub type NSTrackingRectTag = NonZeroIsize; // NSInteger, but non-zero!
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{
|
||||
CGFloat, NSArray, NSInteger, NSObject, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||
};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||
|
||||
use super::NSResponder;
|
||||
use super::{NSButton, NSColor, NSEvent, NSPasteboardType, NSResponder, NSScreen, NSView};
|
||||
|
||||
extern_class!(
|
||||
/// Main-Thread-Only!
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct NSWindow;
|
||||
|
||||
|
@ -12,3 +18,314 @@ extern_class!(
|
|||
type Super = NSResponder;
|
||||
}
|
||||
);
|
||||
|
||||
// Documented as "Main Thread Only", but:
|
||||
// > Thread safe in that you can create and manage them on a secondary thread.
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47>
|
||||
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-123364>
|
||||
//
|
||||
// So could in theory be `Send`, and perhaps also `Sync` - but we would like
|
||||
// interior mutability on windows, since that's just much easier, and in that
|
||||
// case, they can't be!
|
||||
|
||||
extern_methods!(
|
||||
unsafe impl NSWindow {
|
||||
#[sel(frame)]
|
||||
pub fn frame(&self) -> NSRect;
|
||||
|
||||
#[sel(backingScaleFactor)]
|
||||
pub fn backingScaleFactor(&self) -> CGFloat;
|
||||
|
||||
pub fn contentView(&self) -> Id<NSView, Shared> {
|
||||
unsafe { msg_send_id![self, contentView] }
|
||||
}
|
||||
|
||||
#[sel(setContentView:)]
|
||||
pub fn setContentView(&self, view: &NSView);
|
||||
|
||||
#[sel(setInitialFirstResponder:)]
|
||||
pub fn setInitialFirstResponder(&self, view: &NSView);
|
||||
|
||||
#[sel(makeFirstResponder:)]
|
||||
#[must_use]
|
||||
pub fn makeFirstResponder(&self, responder: Option<&NSResponder>) -> bool;
|
||||
|
||||
#[sel(contentRectForFrameRect:)]
|
||||
pub fn contentRectForFrameRect(&self, windowFrame: NSRect) -> NSRect;
|
||||
|
||||
pub fn screen(&self) -> Option<Id<NSScreen, Shared>> {
|
||||
unsafe { msg_send_id![self, screen] }
|
||||
}
|
||||
|
||||
#[sel(setContentSize:)]
|
||||
pub fn setContentSize(&self, contentSize: NSSize);
|
||||
|
||||
#[sel(setFrameTopLeftPoint:)]
|
||||
pub fn setFrameTopLeftPoint(&self, point: NSPoint);
|
||||
|
||||
#[sel(setMinSize:)]
|
||||
pub fn setMinSize(&self, minSize: NSSize);
|
||||
|
||||
#[sel(setMaxSize:)]
|
||||
pub fn setMaxSize(&self, maxSize: NSSize);
|
||||
|
||||
#[sel(setResizeIncrements:)]
|
||||
pub fn setResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[sel(contentResizeIncrements)]
|
||||
pub fn contentResizeIncrements(&self) -> NSSize;
|
||||
|
||||
#[sel(setContentResizeIncrements:)]
|
||||
pub fn setContentResizeIncrements(&self, increments: NSSize);
|
||||
|
||||
#[sel(setFrame:display:)]
|
||||
pub fn setFrame_display(&self, frameRect: NSRect, flag: bool);
|
||||
|
||||
#[sel(setMovable:)]
|
||||
pub fn setMovable(&self, movable: bool);
|
||||
|
||||
#[sel(setOpaque:)]
|
||||
pub fn setOpaque(&self, opaque: bool);
|
||||
|
||||
#[sel(hasShadow)]
|
||||
pub fn hasShadow(&self) -> bool;
|
||||
|
||||
#[sel(setHasShadow:)]
|
||||
pub fn setHasShadow(&self, has_shadow: bool);
|
||||
|
||||
#[sel(setIgnoresMouseEvents:)]
|
||||
pub fn setIgnoresMouseEvents(&self, ignores: bool);
|
||||
|
||||
#[sel(setBackgroundColor:)]
|
||||
pub fn setBackgroundColor(&self, color: &NSColor);
|
||||
|
||||
#[sel(styleMask)]
|
||||
pub fn styleMask(&self) -> NSWindowStyleMask;
|
||||
|
||||
#[sel(setStyleMask:)]
|
||||
pub fn setStyleMask(&self, mask: NSWindowStyleMask);
|
||||
|
||||
#[sel(registerForDraggedTypes:)]
|
||||
pub fn registerForDraggedTypes(&self, types: &NSArray<NSPasteboardType>);
|
||||
|
||||
#[sel(makeKeyAndOrderFront:)]
|
||||
pub fn makeKeyAndOrderFront(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(miniaturize:)]
|
||||
pub fn miniaturize(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(sender:)]
|
||||
pub fn deminiaturize(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(toggleFullScreen:)]
|
||||
pub fn toggleFullScreen(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(orderOut:)]
|
||||
pub fn orderOut(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(zoom:)]
|
||||
pub fn zoom(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(selectNextKeyView:)]
|
||||
pub fn selectNextKeyView(&self, sender: Option<&Object>);
|
||||
|
||||
#[sel(selectPreviousKeyView:)]
|
||||
pub fn selectPreviousKeyView(&self, sender: Option<&Object>);
|
||||
|
||||
pub fn firstResponder(&self) -> Option<Id<NSResponder, Shared>> {
|
||||
unsafe { msg_send_id![self, firstResponder] }
|
||||
}
|
||||
|
||||
pub fn standardWindowButton(&self, kind: NSWindowButton) -> Option<Id<NSButton, Shared>> {
|
||||
unsafe { msg_send_id![self, standardWindowButton: kind] }
|
||||
}
|
||||
|
||||
#[sel(setTitle:)]
|
||||
pub fn setTitle(&self, title: &NSString);
|
||||
|
||||
#[sel(setReleasedWhenClosed:)]
|
||||
pub fn setReleasedWhenClosed(&self, val: bool);
|
||||
|
||||
#[sel(setAcceptsMouseMovedEvents:)]
|
||||
pub fn setAcceptsMouseMovedEvents(&self, val: bool);
|
||||
|
||||
#[sel(setTitlebarAppearsTransparent:)]
|
||||
pub fn setTitlebarAppearsTransparent(&self, val: bool);
|
||||
|
||||
#[sel(setTitleVisibility:)]
|
||||
pub fn setTitleVisibility(&self, visibility: NSWindowTitleVisibility);
|
||||
|
||||
#[sel(setMovableByWindowBackground:)]
|
||||
pub fn setMovableByWindowBackground(&self, val: bool);
|
||||
|
||||
#[sel(setLevel:)]
|
||||
pub fn setLevel(&self, level: NSWindowLevel);
|
||||
|
||||
#[sel(occlusionState)]
|
||||
pub fn occlusionState(&self) -> NSWindowOcclusionState;
|
||||
|
||||
#[sel(center)]
|
||||
pub fn center(&self);
|
||||
|
||||
#[sel(isResizable)]
|
||||
pub fn isResizable(&self) -> bool;
|
||||
|
||||
#[sel(isMiniaturized)]
|
||||
pub fn isMiniaturized(&self) -> bool;
|
||||
|
||||
#[sel(isVisible)]
|
||||
pub fn isVisible(&self) -> bool;
|
||||
|
||||
#[sel(isZoomed)]
|
||||
pub fn isZoomed(&self) -> bool;
|
||||
|
||||
#[sel(close)]
|
||||
pub fn close(&self);
|
||||
|
||||
#[sel(performWindowDragWithEvent:)]
|
||||
// TODO: Can this actually accept NULL?
|
||||
pub fn performWindowDragWithEvent(&self, event: Option<&NSEvent>);
|
||||
|
||||
#[sel(invalidateCursorRectsForView:)]
|
||||
pub fn invalidateCursorRectsForView(&self, view: &NSView);
|
||||
|
||||
#[sel(setDelegate:)]
|
||||
pub fn setDelegate(&self, delegate: Option<&NSObject>);
|
||||
|
||||
#[sel(sendEvent:)]
|
||||
pub unsafe fn sendEvent(&self, event: &NSEvent);
|
||||
}
|
||||
);
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(isize)] // NSInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowTitleVisibility {
|
||||
#[doc(alias = "NSWindowTitleVisible")]
|
||||
Visible = 0,
|
||||
#[doc(alias = "NSWindowTitleHidden")]
|
||||
Hidden = 1,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowTitleVisibility {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSWindowButton {
|
||||
#[doc(alias = "NSWindowCloseButton")]
|
||||
Close = 0,
|
||||
#[doc(alias = "NSWindowMiniaturizeButton")]
|
||||
Miniaturize = 1,
|
||||
#[doc(alias = "NSWindowZoomButton")]
|
||||
Zoom = 2,
|
||||
#[doc(alias = "NSWindowToolbarButton")]
|
||||
Toolbar = 3,
|
||||
#[doc(alias = "NSWindowDocumentIconButton")]
|
||||
DocumentIcon = 4,
|
||||
#[doc(alias = "NSWindowDocumentVersionsButton")]
|
||||
DocumentVersions = 6,
|
||||
#[doc(alias = "NSWindowFullScreenButton")]
|
||||
#[deprecated = "Deprecated since macOS 10.12"]
|
||||
FullScreen = 7,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowButton {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod window_level_key {
|
||||
use objc2::foundation::NSInteger;
|
||||
pub const kCGBaseWindowLevelKey: NSInteger = 0;
|
||||
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
|
||||
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
|
||||
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
|
||||
pub const kCGNormalWindowLevelKey: NSInteger = 4;
|
||||
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
|
||||
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
|
||||
pub const kCGDockWindowLevelKey: NSInteger = 7;
|
||||
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
|
||||
pub const kCGStatusWindowLevelKey: NSInteger = 9;
|
||||
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
|
||||
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
|
||||
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
|
||||
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
|
||||
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
|
||||
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
|
||||
pub const kCGHelpWindowLevelKey: NSInteger = 16;
|
||||
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
|
||||
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
|
||||
pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
||||
}
|
||||
use window_level_key::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[repr(isize)]
|
||||
pub enum NSWindowLevel {
|
||||
#[doc(alias = "NSNormalWindowLevel")]
|
||||
Normal = kCGBaseWindowLevelKey,
|
||||
#[doc(alias = "NSFloatingWindowLevel")]
|
||||
Floating = kCGFloatingWindowLevelKey,
|
||||
#[doc(alias = "NSTornOffMenuWindowLevel")]
|
||||
TornOffMenu = kCGTornOffMenuWindowLevelKey,
|
||||
#[doc(alias = "NSModalPanelWindowLevel")]
|
||||
ModalPanel = kCGModalPanelWindowLevelKey,
|
||||
#[doc(alias = "NSMainMenuWindowLevel")]
|
||||
MainMenu = kCGMainMenuWindowLevelKey,
|
||||
#[doc(alias = "NSStatusWindowLevel")]
|
||||
Status = kCGStatusWindowLevelKey,
|
||||
#[doc(alias = "NSPopUpMenuWindowLevel")]
|
||||
PopUpMenu = kCGPopUpMenuWindowLevelKey,
|
||||
#[doc(alias = "NSScreenSaverWindowLevel")]
|
||||
ScreenSaver = kCGScreenSaverWindowLevelKey,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowLevel {
|
||||
const ENCODING: Encoding = NSInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct NSWindowOcclusionState: NSUInteger {
|
||||
const NSWindowOcclusionStateVisible = 1 << 1;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowOcclusionState {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct NSWindowStyleMask: NSUInteger {
|
||||
const NSBorderlessWindowMask = 0;
|
||||
const NSTitledWindowMask = 1 << 0;
|
||||
const NSClosableWindowMask = 1 << 1;
|
||||
const NSMiniaturizableWindowMask = 1 << 2;
|
||||
const NSResizableWindowMask = 1 << 3;
|
||||
const NSTexturedBackgroundWindowMask = 1 << 8;
|
||||
const NSUnifiedTitleAndToolbarWindowMask = 1 << 12;
|
||||
const NSFullScreenWindowMask = 1 << 14;
|
||||
const NSFullSizeContentViewWindowMask = 1 << 15;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSWindowStyleMask {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[repr(usize)] // NSUInteger
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum NSBackingStoreType {
|
||||
NSBackingStoreRetained = 0,
|
||||
NSBackingStoreNonretained = 1,
|
||||
NSBackingStoreBuffered = 2,
|
||||
}
|
||||
|
||||
unsafe impl Encode for NSBackingStoreType {
|
||||
const ENCODING: Encoding = NSUInteger::ENCODING;
|
||||
}
|
||||
|
|
|
@ -1,29 +1,25 @@
|
|||
use std::os::raw::c_ushort;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSEvent, NSEventModifierFlags},
|
||||
base::id,
|
||||
};
|
||||
use objc2::rc::{Id, Shared};
|
||||
|
||||
use super::appkit::{NSEvent, NSEventModifierFlags};
|
||||
use super::window::WinitWindow;
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent},
|
||||
platform_impl::platform::{
|
||||
util::{IdRef, Never},
|
||||
DEVICE_ID,
|
||||
},
|
||||
platform_impl::platform::{util::Never, DEVICE_ID},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventWrapper {
|
||||
pub(crate) enum EventWrapper {
|
||||
StaticEvent(Event<'static, Never>),
|
||||
EventProxy(EventProxy),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EventProxy {
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum EventProxy {
|
||||
DpiChangedProxy {
|
||||
ns_window: IdRef,
|
||||
window: Id<WinitWindow, Shared>,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
|
@ -241,8 +237,8 @@ pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn event_mods(event: id) -> ModifiersState {
|
||||
let flags = unsafe { NSEvent::modifierFlags(event) };
|
||||
pub(super) fn event_mods(event: &NSEvent) -> ModifiersState {
|
||||
let flags = event.modifierFlags();
|
||||
let mut m = ModifiersState::empty();
|
||||
m.set(
|
||||
ModifiersState::SHIFT,
|
||||
|
@ -263,21 +259,13 @@ pub fn event_mods(event: id) -> ModifiersState {
|
|||
m
|
||||
}
|
||||
|
||||
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
|
||||
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
|
||||
// and there is no easy way to navtively retrieve the layout-dependent character.
|
||||
// In winit, we use keycode to refer to the key's character, and so this function aligns
|
||||
// AppKit's terminology with ours.
|
||||
unsafe { msg_send![event, keyCode] }
|
||||
}
|
||||
|
||||
pub unsafe fn modifier_event(
|
||||
ns_event: id,
|
||||
pub(super) fn modifier_event(
|
||||
event: &NSEvent,
|
||||
keymask: NSEventModifierFlags,
|
||||
was_key_pressed: bool,
|
||||
) -> Option<WindowEvent<'static>> {
|
||||
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|
||||
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask)
|
||||
if !was_key_pressed && event.modifierFlags().contains(keymask)
|
||||
|| was_key_pressed && !event.modifierFlags().contains(keymask)
|
||||
{
|
||||
let state = if was_key_pressed {
|
||||
ElementState::Released
|
||||
|
@ -285,7 +273,7 @@ pub unsafe fn modifier_event(
|
|||
ElementState::Pressed
|
||||
};
|
||||
|
||||
let scancode = get_scancode(ns_event);
|
||||
let scancode = event.scancode();
|
||||
let virtual_keycode = scancode_to_keycode(scancode);
|
||||
#[allow(deprecated)]
|
||||
Some(WindowEvent::KeyboardInput {
|
||||
|
@ -294,7 +282,7 @@ pub unsafe fn modifier_event(
|
|||
state,
|
||||
scancode: scancode as _,
|
||||
virtual_keycode,
|
||||
modifiers: event_mods(ns_event),
|
||||
modifiers: event_mods(event),
|
||||
},
|
||||
is_synthetic: false,
|
||||
})
|
||||
|
|
|
@ -11,16 +11,12 @@ use std::{
|
|||
sync::mpsc,
|
||||
};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined},
|
||||
base::{id, nil},
|
||||
foundation::{NSPoint, NSTimeInterval},
|
||||
};
|
||||
use objc2::foundation::is_main_thread;
|
||||
use objc2::rc::{autoreleasepool, Id, Shared};
|
||||
use objc2::ClassType;
|
||||
use objc2::{msg_send_id, ClassType};
|
||||
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
|
||||
|
||||
use super::appkit::{NSApp, NSApplicationActivationPolicy, NSEvent};
|
||||
use crate::{
|
||||
event::Event,
|
||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
|
||||
|
@ -95,15 +91,11 @@ impl<T: 'static> EventLoopWindowTarget<T> {
|
|||
|
||||
impl<T> EventLoopWindowTarget<T> {
|
||||
pub(crate) fn hide_application(&self) {
|
||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
||||
unsafe { msg_send![app, hide: 0] }
|
||||
NSApp().hide(None)
|
||||
}
|
||||
|
||||
pub(crate) fn hide_other_applications(&self) {
|
||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
||||
unsafe { msg_send![app, hideOtherApplications: 0] }
|
||||
NSApp().hideOtherApplications(None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,31 +133,29 @@ impl Default for PlatformSpecificEventLoopAttributes {
|
|||
|
||||
impl<T> EventLoop<T> {
|
||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
||||
let delegate = unsafe {
|
||||
if !is_main_thread() {
|
||||
panic!("On macOS, `EventLoop` must be created on the main thread!");
|
||||
}
|
||||
if !is_main_thread() {
|
||||
panic!("On macOS, `EventLoop` must be created on the main thread!");
|
||||
}
|
||||
|
||||
// This must be done before `NSApp()` (equivalent to sending
|
||||
// `sharedApplication`) is called anywhere else, or we'll end up
|
||||
// with the wrong `NSApplication` class and the wrong thread could
|
||||
// be marked as main.
|
||||
let app: id = msg_send![WinitApplication::class(), sharedApplication];
|
||||
// This must be done before `NSApp()` (equivalent to sending
|
||||
// `sharedApplication`) is called anywhere else, or we'll end up
|
||||
// with the wrong `NSApplication` class and the wrong thread could
|
||||
// be marked as main.
|
||||
let app: Id<WinitApplication, Shared> =
|
||||
unsafe { msg_send_id![WinitApplication::class(), sharedApplication] };
|
||||
|
||||
use cocoa::appkit::NSApplicationActivationPolicy::*;
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
};
|
||||
let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu);
|
||||
|
||||
autoreleasepool(|_| {
|
||||
let _: () = msg_send![app, setDelegate: &*delegate];
|
||||
});
|
||||
|
||||
delegate
|
||||
use NSApplicationActivationPolicy::*;
|
||||
let activation_policy = match attributes.activation_policy {
|
||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
||||
};
|
||||
let delegate = ApplicationDelegate::new(activation_policy, attributes.default_menu);
|
||||
|
||||
autoreleasepool(|_| {
|
||||
app.setDelegate(&delegate);
|
||||
});
|
||||
|
||||
let panic_info: Rc<PanicInfo> = Default::default();
|
||||
setup_control_flow_observers(Rc::downgrade(&panic_info));
|
||||
EventLoop {
|
||||
|
@ -208,9 +198,8 @@ impl<T> EventLoop<T> {
|
|||
|
||||
self._callback = Some(Rc::clone(&callback));
|
||||
|
||||
let exit_code = autoreleasepool(|_| unsafe {
|
||||
let exit_code = autoreleasepool(|_| {
|
||||
let app = NSApp();
|
||||
assert_ne!(app, nil);
|
||||
|
||||
// A bit of juggling with the callback references to make sure
|
||||
// that `self.callback` is the only owner of the callback.
|
||||
|
@ -218,7 +207,7 @@ impl<T> EventLoop<T> {
|
|||
drop(callback);
|
||||
|
||||
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
|
||||
let _: () = msg_send![app, run];
|
||||
unsafe { app.run() };
|
||||
|
||||
if let Some(panic) = self.panic_info.take() {
|
||||
drop(self._callback.take());
|
||||
|
@ -236,24 +225,6 @@ impl<T> EventLoop<T> {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub unsafe fn post_dummy_event(target: id) {
|
||||
let event_class = class!(NSEvent);
|
||||
let dummy_event: id = msg_send![
|
||||
event_class,
|
||||
otherEventWithType: NSApplicationDefined
|
||||
location: NSPoint::new(0.0, 0.0)
|
||||
modifierFlags: NSEventModifierFlags::empty()
|
||||
timestamp: 0 as NSTimeInterval
|
||||
windowNumber: 0isize
|
||||
context: nil
|
||||
subtype: NSEventSubtype::NSWindowExposedEventType
|
||||
data1: 0isize
|
||||
data2: 0isize
|
||||
];
|
||||
let _: () = msg_send![target, postEvent: dummy_event, atStart: true];
|
||||
}
|
||||
|
||||
/// Catches panics that happen inside `f` and when a panic
|
||||
/// happens, stops the `sharedApplication`
|
||||
#[inline]
|
||||
|
@ -272,15 +243,11 @@ pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
|||
let panic_info = panic_info.upgrade().unwrap();
|
||||
panic_info.set_panic(e);
|
||||
}
|
||||
unsafe {
|
||||
let app_class = class!(NSApplication);
|
||||
let app: id = msg_send![app_class, sharedApplication];
|
||||
let _: () = msg_send![app, stop: nil];
|
||||
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
post_dummy_event(app);
|
||||
}
|
||||
let app = NSApp();
|
||||
app.stop(None);
|
||||
// Posting a dummy event to get `stop` to take effect immediately.
|
||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
||||
app.postEvent_atStart(&NSEvent::dummy(), true);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
use std::ffi::c_void;
|
||||
|
||||
use cocoa::base::id;
|
||||
use core_foundation::{
|
||||
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
|
||||
};
|
||||
|
@ -12,85 +11,6 @@ use core_graphics::{
|
|||
base::CGError,
|
||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||
};
|
||||
use objc::foundation::{NSInteger, NSUInteger};
|
||||
|
||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||
|
||||
pub trait NSMutableAttributedString: Sized {
|
||||
unsafe fn alloc(_: Self) -> id {
|
||||
msg_send![class!(NSMutableAttributedString), alloc]
|
||||
}
|
||||
|
||||
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
|
||||
unsafe fn initWithString(self, string: id) -> id;
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id;
|
||||
|
||||
unsafe fn string(self) -> id; // *mut NSString
|
||||
unsafe fn mutableString(self) -> id; // *mut NSMutableString
|
||||
unsafe fn length(self) -> NSUInteger;
|
||||
}
|
||||
|
||||
impl NSMutableAttributedString for id {
|
||||
unsafe fn init(self) -> id {
|
||||
msg_send![self, init]
|
||||
}
|
||||
|
||||
unsafe fn initWithString(self, string: id) -> id {
|
||||
msg_send![self, initWithString: string]
|
||||
}
|
||||
|
||||
unsafe fn initWithAttributedString(self, string: id) -> id {
|
||||
msg_send![self, initWithAttributedString: string]
|
||||
}
|
||||
|
||||
unsafe fn string(self) -> id {
|
||||
msg_send![self, string]
|
||||
}
|
||||
|
||||
unsafe fn mutableString(self) -> id {
|
||||
msg_send![self, mutableString]
|
||||
}
|
||||
|
||||
unsafe fn length(self) -> NSUInteger {
|
||||
msg_send![self, length]
|
||||
}
|
||||
}
|
||||
|
||||
pub const kCGBaseWindowLevelKey: NSInteger = 0;
|
||||
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
|
||||
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
|
||||
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
|
||||
pub const kCGNormalWindowLevelKey: NSInteger = 4;
|
||||
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
|
||||
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
|
||||
pub const kCGDockWindowLevelKey: NSInteger = 7;
|
||||
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
|
||||
pub const kCGStatusWindowLevelKey: NSInteger = 9;
|
||||
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
|
||||
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
|
||||
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
|
||||
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
|
||||
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
|
||||
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
|
||||
pub const kCGHelpWindowLevelKey: NSInteger = 16;
|
||||
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
|
||||
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
|
||||
pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(isize)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum NSWindowLevel {
|
||||
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
|
||||
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
|
||||
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
|
||||
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
|
||||
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
|
||||
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
|
||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||
}
|
||||
|
||||
pub type CGDisplayFadeInterval = f32;
|
||||
pub type CGDisplayReservationInterval = f32;
|
||||
|
|
|
@ -1,115 +1,98 @@
|
|||
use super::util::IdRef;
|
||||
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
use cocoa::base::{nil, selector};
|
||||
use cocoa::foundation::{NSProcessInfo, NSString};
|
||||
use objc::{
|
||||
rc::autoreleasepool,
|
||||
runtime::{Object, Sel},
|
||||
};
|
||||
use objc2::foundation::{NSProcessInfo, NSString};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::runtime::Sel;
|
||||
use objc2::{ns_string, sel};
|
||||
|
||||
use super::appkit::{NSApp, NSEventModifierFlags, NSMenu, NSMenuItem};
|
||||
|
||||
struct KeyEquivalent<'a> {
|
||||
key: &'a str,
|
||||
key: &'a NSString,
|
||||
masks: Option<NSEventModifierFlags>,
|
||||
}
|
||||
|
||||
pub fn initialize() {
|
||||
autoreleasepool(|_| unsafe {
|
||||
let menubar = IdRef::new(NSMenu::new(nil));
|
||||
let app_menu_item = IdRef::new(NSMenuItem::new(nil));
|
||||
menubar.addItem_(*app_menu_item);
|
||||
let app = NSApp();
|
||||
app.setMainMenu_(*menubar);
|
||||
let menubar = NSMenu::new();
|
||||
let app_menu_item = NSMenuItem::new();
|
||||
menubar.addItem(&app_menu_item);
|
||||
|
||||
let app_menu = NSMenu::new(nil);
|
||||
let process_name = NSProcessInfo::processInfo(nil).processName();
|
||||
let app_menu = NSMenu::new();
|
||||
let process_name = NSProcessInfo::process_info().process_name();
|
||||
|
||||
// About menu item
|
||||
let about_item_prefix = NSString::alloc(nil).init_str("About ");
|
||||
let about_item_title = about_item_prefix.stringByAppendingString_(process_name);
|
||||
let about_item = menu_item(
|
||||
about_item_title,
|
||||
selector("orderFrontStandardAboutPanel:"),
|
||||
None,
|
||||
);
|
||||
// About menu item
|
||||
let about_item_title = ns_string!("About ").concat(&*process_name);
|
||||
let about_item = menu_item(&about_item_title, sel!(orderFrontStandardAboutPanel:), None);
|
||||
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem(nil);
|
||||
// Seperator menu item
|
||||
let sep_first = NSMenuItem::separatorItem();
|
||||
|
||||
// Hide application menu item
|
||||
let hide_item_prefix = NSString::alloc(nil).init_str("Hide ");
|
||||
let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name);
|
||||
let hide_item = menu_item(
|
||||
hide_item_title,
|
||||
selector("hide:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "h",
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
// Hide application menu item
|
||||
let hide_item_title = ns_string!("Hide ").concat(&*process_name);
|
||||
let hide_item = menu_item(
|
||||
&hide_item_title,
|
||||
sel!(hide:),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
|
||||
// Hide other applications menu item
|
||||
let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
hide_others_item_title,
|
||||
selector("hideOtherApplications:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "h",
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSAlternateKeyMask
|
||||
| NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
}),
|
||||
);
|
||||
// Hide other applications menu item
|
||||
let hide_others_item_title = ns_string!("Hide Others");
|
||||
let hide_others_item = menu_item(
|
||||
hide_others_item_title,
|
||||
sel!(hideOtherApplications:),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("h"),
|
||||
masks: Some(
|
||||
NSEventModifierFlags::NSAlternateKeyMask | NSEventModifierFlags::NSCommandKeyMask,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
// Show applications menu item
|
||||
let show_all_item_title = NSString::alloc(nil).init_str("Show All");
|
||||
let show_all_item = menu_item(
|
||||
show_all_item_title,
|
||||
selector("unhideAllApplications:"),
|
||||
None,
|
||||
);
|
||||
// Show applications menu item
|
||||
let show_all_item_title = ns_string!("Show All");
|
||||
let show_all_item = menu_item(show_all_item_title, sel!(unhideAllApplications:), None);
|
||||
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem(nil);
|
||||
// Seperator menu item
|
||||
let sep = NSMenuItem::separatorItem();
|
||||
|
||||
// Quit application menu item
|
||||
let quit_item_prefix = NSString::alloc(nil).init_str("Quit ");
|
||||
let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name);
|
||||
let quit_item = menu_item(
|
||||
quit_item_title,
|
||||
selector("terminate:"),
|
||||
Some(KeyEquivalent {
|
||||
key: "q",
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
// Quit application menu item
|
||||
let quit_item_title = ns_string!("Quit ").concat(&*process_name);
|
||||
let quit_item = menu_item(
|
||||
&quit_item_title,
|
||||
sel!(terminate:),
|
||||
Some(KeyEquivalent {
|
||||
key: ns_string!("q"),
|
||||
masks: None,
|
||||
}),
|
||||
);
|
||||
|
||||
app_menu.addItem_(about_item);
|
||||
app_menu.addItem_(sep_first);
|
||||
app_menu.addItem_(hide_item);
|
||||
app_menu.addItem_(hide_others_item);
|
||||
app_menu.addItem_(show_all_item);
|
||||
app_menu.addItem_(sep);
|
||||
app_menu.addItem_(quit_item);
|
||||
app_menu_item.setSubmenu_(app_menu);
|
||||
});
|
||||
app_menu.addItem(&about_item);
|
||||
app_menu.addItem(&sep_first);
|
||||
app_menu.addItem(&hide_item);
|
||||
app_menu.addItem(&hide_others_item);
|
||||
app_menu.addItem(&show_all_item);
|
||||
app_menu.addItem(&sep);
|
||||
app_menu.addItem(&quit_item);
|
||||
app_menu_item.setSubmenu(&app_menu);
|
||||
|
||||
let app = NSApp();
|
||||
app.setMainMenu(&menubar);
|
||||
}
|
||||
|
||||
fn menu_item(
|
||||
title: *mut Object,
|
||||
title: &NSString,
|
||||
selector: Sel,
|
||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
||||
) -> *mut Object {
|
||||
unsafe {
|
||||
let (key, masks) = match key_equivalent {
|
||||
Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks),
|
||||
None => (NSString::alloc(nil).init_str(""), None),
|
||||
};
|
||||
let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key);
|
||||
if let Some(masks) = masks {
|
||||
item.setKeyEquivalentModifierMask_(masks)
|
||||
}
|
||||
|
||||
item
|
||||
) -> Id<NSMenuItem, Shared> {
|
||||
let (key, masks) = match key_equivalent {
|
||||
Some(ke) => (ke.key, ke.masks),
|
||||
None => (ns_string!(""), None),
|
||||
};
|
||||
let item = NSMenuItem::newWithTitle(title, selector, key);
|
||||
if let Some(masks) = masks {
|
||||
item.setKeyEquivalentModifierMask(masks)
|
||||
}
|
||||
|
||||
item
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#![cfg(target_os = "macos")]
|
||||
#![allow(clippy::let_unit_value)]
|
||||
#![deny(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
#[macro_use]
|
||||
mod util;
|
||||
|
@ -18,19 +17,21 @@ mod view;
|
|||
mod window;
|
||||
mod window_delegate;
|
||||
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
use std::{fmt, ops::Deref};
|
||||
|
||||
use self::window::WinitWindow;
|
||||
use self::window_delegate::WinitWindowDelegate;
|
||||
pub(crate) use self::{
|
||||
event_loop::{
|
||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
||||
},
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId},
|
||||
window::{PlatformSpecificWindowBuilderAttributes, WindowId},
|
||||
};
|
||||
use crate::{
|
||||
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
|
||||
};
|
||||
use objc::rc::autoreleasepool;
|
||||
use objc2::rc::{autoreleasepool, Id, Shared};
|
||||
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
|
||||
|
@ -46,10 +47,17 @@ impl DeviceId {
|
|||
// Constant device ID; to be removed when if backend is updated to report real device IDs.
|
||||
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
|
||||
|
||||
pub struct Window {
|
||||
window: Arc<UnownedWindow>,
|
||||
pub(crate) struct Window {
|
||||
pub(crate) window: Id<WinitWindow, Shared>,
|
||||
// We keep this around so that it doesn't get dropped until the window does.
|
||||
_delegate: util::IdRef,
|
||||
_delegate: Id<WinitWindowDelegate, Shared>,
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
// Ensure the window is closed
|
||||
util::close_async(Id::into_super(self.window.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -62,7 +70,7 @@ unsafe impl Send for Window {}
|
|||
unsafe impl Sync for Window {}
|
||||
|
||||
impl Deref for Window {
|
||||
type Target = UnownedWindow;
|
||||
type Target = WinitWindow;
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.window
|
||||
|
@ -75,7 +83,7 @@ impl Window {
|
|||
attributes: WindowAttributes,
|
||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Self, RootOsError> {
|
||||
let (window, _delegate) = autoreleasepool(|_| UnownedWindow::new(attributes, pl_attribs))?;
|
||||
let (window, _delegate) = autoreleasepool(|_| WinitWindow::new(attributes, pl_attribs))?;
|
||||
Ok(Window { window, _delegate })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
use std::{collections::VecDeque, fmt};
|
||||
|
||||
use super::{ffi, util};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::NSScreen,
|
||||
base::{id, nil},
|
||||
};
|
||||
use core_foundation::{
|
||||
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
||||
base::{CFRelease, TCFType},
|
||||
string::CFString,
|
||||
};
|
||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||
use objc::foundation::NSUInteger;
|
||||
use objc2::rc::{Id, Shared};
|
||||
|
||||
use super::appkit::NSScreen;
|
||||
use super::ffi;
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VideoMode {
|
||||
|
@ -213,11 +211,10 @@ impl MonitorHandle {
|
|||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
let screen = match self.ns_screen() {
|
||||
Some(screen) => screen,
|
||||
None => return 1.0, // default to 1.0 when we can't find the screen
|
||||
};
|
||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
||||
match self.ns_screen() {
|
||||
Some(screen) => screen.backingScaleFactor() as f64,
|
||||
None => 1.0, // default to 1.0 when we can't find the screen
|
||||
}
|
||||
}
|
||||
|
||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
||||
|
@ -298,26 +295,19 @@ impl MonitorHandle {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ns_screen(&self) -> Option<id> {
|
||||
unsafe {
|
||||
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||
let screens = NSScreen::screens(nil);
|
||||
let count: NSUInteger = msg_send![screens, count];
|
||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
||||
for i in 0..count {
|
||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||
let device_description = NSScreen::deviceDescription(screen);
|
||||
let value: id = msg_send![device_description, objectForKey:*key];
|
||||
if value != nil {
|
||||
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||
let other_uuid =
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
|
||||
if uuid == other_uuid {
|
||||
return Some(screen);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
pub(crate) fn ns_screen(&self) -> Option<Id<NSScreen, Shared>> {
|
||||
let uuid = unsafe { ffi::CGDisplayCreateUUIDFromDisplayID(self.0) };
|
||||
NSScreen::screens()
|
||||
.into_iter()
|
||||
.find(|screen| {
|
||||
let other_native_id = screen.display_id();
|
||||
let other_uuid = unsafe {
|
||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID)
|
||||
};
|
||||
uuid == other_uuid
|
||||
})
|
||||
.map(|screen| unsafe {
|
||||
Id::retain(screen as *const NSScreen as *mut NSScreen).unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
|||
where
|
||||
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
|
||||
{
|
||||
let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo);
|
||||
let info_from_raw = unsafe { Weak::from_raw(panic_info as *mut PanicInfo) };
|
||||
// Asserting unwind safety on this type should be fine because `PanicInfo` is
|
||||
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
|
||||
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
|
||||
|
@ -195,7 +195,7 @@ struct RunLoop(CFRunLoopRef);
|
|||
|
||||
impl RunLoop {
|
||||
unsafe fn get() -> Self {
|
||||
RunLoop(CFRunLoopGetMain())
|
||||
RunLoop(unsafe { CFRunLoopGetMain() })
|
||||
}
|
||||
|
||||
unsafe fn add_observer(
|
||||
|
@ -205,15 +205,17 @@ impl RunLoop {
|
|||
handler: CFRunLoopObserverCallBack,
|
||||
context: *mut CFRunLoopObserverContext,
|
||||
) {
|
||||
let observer = CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
context,
|
||||
);
|
||||
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
|
||||
let observer = unsafe {
|
||||
CFRunLoopObserverCreate(
|
||||
ptr::null_mut(),
|
||||
flags,
|
||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||
priority, // The lower the value, the sooner this will run
|
||||
handler,
|
||||
context,
|
||||
)
|
||||
};
|
||||
unsafe { CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes) };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +1,16 @@
|
|||
use std::{
|
||||
ops::Deref,
|
||||
sync::{Mutex, Weak},
|
||||
};
|
||||
use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Mutex, Weak};
|
||||
|
||||
use cocoa::{
|
||||
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSPoint, NSSize, NSString},
|
||||
};
|
||||
use dispatch::Queue;
|
||||
use objc::foundation::is_main_thread;
|
||||
use objc::rc::autoreleasepool;
|
||||
use objc::runtime::Bool;
|
||||
use objc2::foundation::{is_main_thread, CGFloat, NSPoint, NSSize, NSString};
|
||||
use objc2::rc::{autoreleasepool, Id, Shared};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform_impl::platform::{
|
||||
appkit::{NSScreen, NSWindow, NSWindowLevel, NSWindowStyleMask},
|
||||
ffi,
|
||||
util::IdRef,
|
||||
window::{SharedState, SharedStateMutexGuard},
|
||||
},
|
||||
};
|
||||
|
@ -37,91 +30,88 @@ impl<T> Deref for MainThreadSafe<T> {
|
|||
}
|
||||
}
|
||||
|
||||
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
ns_window.setStyleMask_(mask);
|
||||
fn set_style_mask(window: &NSWindow, mask: NSWindowStyleMask) {
|
||||
window.setStyleMask(mask);
|
||||
// If we don't do this, key handling will break
|
||||
// (at least until the window is clicked again/etc.)
|
||||
ns_window.makeFirstResponder_(ns_view);
|
||||
let _ = window.makeFirstResponder(Some(&window.contentView()));
|
||||
}
|
||||
|
||||
// Always use this function instead of trying to modify `styleMask` directly!
|
||||
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
|
||||
// Otherwise, this would vomit out errors about not being on the main thread
|
||||
// and fail to do anything.
|
||||
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
pub(crate) fn set_style_mask_async(window: &NSWindow, mask: NSWindowStyleMask) {
|
||||
// TODO(madsmtm): Remove this 'static hack!
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
set_style_mask(*window, mask);
|
||||
});
|
||||
}
|
||||
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
||||
pub(crate) fn set_style_mask_sync(window: &NSWindow, mask: NSWindowStyleMask) {
|
||||
if is_main_thread() {
|
||||
set_style_mask(ns_window, ns_view, mask);
|
||||
set_style_mask(window, mask);
|
||||
} else {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
let window = MainThreadSafe(window);
|
||||
Queue::main().exec_sync(move || {
|
||||
set_style_mask(*ns_window, *ns_view, mask);
|
||||
set_style_mask(*window, mask);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
|
||||
// and just fails silently. Anyway, GCD to the rescue!
|
||||
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn set_content_size_async(window: &NSWindow, size: LogicalSize<f64>) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
||||
window.setContentSize(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
||||
});
|
||||
}
|
||||
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
|
||||
// to log errors.
|
||||
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn set_frame_top_left_point_async(window: &NSWindow, point: NSPoint) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setFrameTopLeftPoint_(point);
|
||||
window.setFrameTopLeftPoint(point);
|
||||
});
|
||||
}
|
||||
|
||||
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
|
||||
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn set_level_async(window: &NSWindow, level: NSWindowLevel) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setLevel_(level as _);
|
||||
window.setLevel(level);
|
||||
});
|
||||
}
|
||||
|
||||
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
|
||||
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn set_ignore_mouse_events(window: &NSWindow, ignore: bool) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.setIgnoresMouseEvents_(Bool::from(ignore).as_raw());
|
||||
window.setIgnoresMouseEvents(ignore);
|
||||
});
|
||||
}
|
||||
|
||||
// `toggleFullScreen` is thread-safe, but our additional logic to account for
|
||||
// window styles isn't.
|
||||
pub unsafe fn toggle_full_screen_async(
|
||||
ns_window: id,
|
||||
ns_view: id,
|
||||
pub(crate) fn toggle_full_screen_async(
|
||||
window: &NSWindow,
|
||||
not_fullscreen: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let ns_view = MainThreadSafe(ns_view);
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
||||
// set a normal style temporarily. The previous state will be
|
||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
||||
if not_fullscreen {
|
||||
let curr_mask = ns_window.styleMask();
|
||||
let curr_mask = window.styleMask();
|
||||
let required =
|
||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
||||
if !curr_mask.contains(required) {
|
||||
set_style_mask(*ns_window, *ns_view, required);
|
||||
set_style_mask(*window, required);
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
let mut shared_state_lock = SharedStateMutexGuard::new(
|
||||
shared_state.lock().unwrap(),
|
||||
|
@ -134,26 +124,29 @@ pub unsafe fn toggle_full_screen_async(
|
|||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
ns_window.setLevel_(0);
|
||||
ns_window.toggleFullScreen_(nil);
|
||||
window.setLevel(NSWindowLevel::Normal);
|
||||
window.toggleFullScreen(None);
|
||||
});
|
||||
}
|
||||
|
||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
pub(crate) unsafe fn restore_display_mode_async(ns_screen: u32) {
|
||||
Queue::main().exec_async(move || {
|
||||
ffi::CGRestorePermanentDisplayConfiguration();
|
||||
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
|
||||
unsafe { ffi::CGRestorePermanentDisplayConfiguration() };
|
||||
assert_eq!(
|
||||
unsafe { ffi::CGDisplayRelease(ns_screen) },
|
||||
ffi::kCGErrorSuccess
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// `setMaximized` is not thread-safe
|
||||
pub unsafe fn set_maximized_async(
|
||||
ns_window: id,
|
||||
pub(crate) fn set_maximized_async(
|
||||
window: &NSWindow,
|
||||
is_zoomed: bool,
|
||||
maximized: bool,
|
||||
shared_state: Weak<Mutex<SharedState>>,
|
||||
) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
let shared_state = MainThreadSafe(shared_state);
|
||||
Queue::main().exec_async(move || {
|
||||
if let Some(shared_state) = shared_state.upgrade() {
|
||||
|
@ -162,7 +155,7 @@ pub unsafe fn set_maximized_async(
|
|||
|
||||
// Save the standard frame sized if it is not zoomed
|
||||
if !is_zoomed {
|
||||
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
|
||||
shared_state_lock.standard_frame = Some(window.frame());
|
||||
}
|
||||
|
||||
shared_state_lock.maximized = maximized;
|
||||
|
@ -172,21 +165,21 @@ pub unsafe fn set_maximized_async(
|
|||
return;
|
||||
}
|
||||
|
||||
if ns_window
|
||||
if window
|
||||
.styleMask()
|
||||
.contains(NSWindowStyleMask::NSResizableWindowMask)
|
||||
{
|
||||
// Just use the native zoom if resizable
|
||||
ns_window.zoom_(nil);
|
||||
window.zoom(None);
|
||||
} else {
|
||||
// if it's not resizable, we set the frame directly
|
||||
let new_rect = if maximized {
|
||||
let screen = NSScreen::mainScreen(nil);
|
||||
NSScreen::visibleFrame(screen)
|
||||
let screen = NSScreen::main().expect("no screen found");
|
||||
screen.visibleFrame()
|
||||
} else {
|
||||
shared_state_lock.saved_standard_frame()
|
||||
};
|
||||
ns_window.setFrame_display_(new_rect, Bool::NO.as_raw());
|
||||
window.setFrame_display(new_rect, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -194,30 +187,29 @@ pub unsafe fn set_maximized_async(
|
|||
|
||||
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
|
||||
// but with an odd delay.
|
||||
pub unsafe fn order_out_async(ns_window: id) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn order_out_async(window: &NSWindow) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.orderOut_(nil);
|
||||
window.orderOut(None);
|
||||
});
|
||||
}
|
||||
|
||||
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
|
||||
// actually works, but with an odd delay.
|
||||
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn make_key_and_order_front_async(window: &NSWindow) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
window.makeKeyAndOrderFront(None);
|
||||
});
|
||||
}
|
||||
|
||||
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
|
||||
// window drag regions, which throws an exception when not done in the main
|
||||
// thread
|
||||
pub unsafe fn set_title_async(ns_window: id, title: String) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
pub(crate) fn set_title_async(window: &NSWindow, title: String) {
|
||||
let window = unsafe { MainThreadSafe(mem::transmute::<&NSWindow, &'static NSWindow>(window)) };
|
||||
Queue::main().exec_async(move || {
|
||||
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
|
||||
ns_window.setTitle_(*title);
|
||||
window.setTitle(&NSString::from_str(&title));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -225,12 +217,12 @@ pub unsafe fn set_title_async(ns_window: id, title: String) {
|
|||
// thread. Though, it's a good idea to look into that more...
|
||||
//
|
||||
// ArturKovacs: It's important that this operation keeps the underlying window alive
|
||||
// through the `IdRef` because otherwise it would dereference free'd memory
|
||||
pub unsafe fn close_async(ns_window: IdRef) {
|
||||
let ns_window = MainThreadSafe(ns_window);
|
||||
// through the `Id` because otherwise it would dereference free'd memory
|
||||
pub(crate) fn close_async(window: Id<NSWindow, Shared>) {
|
||||
let window = MainThreadSafe(window);
|
||||
Queue::main().exec_async(move || {
|
||||
autoreleasepool(move |_| {
|
||||
ns_window.close();
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,84 +1,21 @@
|
|||
mod r#async;
|
||||
|
||||
pub use self::r#async::*;
|
||||
pub(crate) use self::r#async::*;
|
||||
|
||||
use std::ops::{BitAnd, Deref};
|
||||
use std::os::raw::c_uchar;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{CGFloat, NSApp, NSWindowStyleMask},
|
||||
base::{id, nil},
|
||||
foundation::{NSPoint, NSRect, NSString},
|
||||
};
|
||||
use core_graphics::display::CGDisplay;
|
||||
use objc2::foundation::{NSRange, NSUInteger};
|
||||
use objc2::foundation::{CGFloat, NSNotFound, NSPoint, NSRange, NSRect, NSUInteger};
|
||||
|
||||
use crate::dpi::LogicalPosition;
|
||||
use crate::platform_impl::platform::ffi;
|
||||
|
||||
// Replace with `!` once stable
|
||||
#[derive(Debug)]
|
||||
pub enum Never {}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
pub const EMPTY_RANGE: NSRange = NSRange {
|
||||
location: ffi::NSNotFound as NSUInteger,
|
||||
location: NSNotFound as NSUInteger,
|
||||
length: 0,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct IdRef(id);
|
||||
|
||||
impl IdRef {
|
||||
pub fn new(inner: id) -> IdRef {
|
||||
IdRef(inner)
|
||||
}
|
||||
|
||||
pub fn retain(inner: id) -> IdRef {
|
||||
if inner != nil {
|
||||
let _: id = unsafe { msg_send![inner, retain] };
|
||||
}
|
||||
IdRef(inner)
|
||||
}
|
||||
|
||||
pub fn non_nil(self) -> Option<IdRef> {
|
||||
if self.0 == nil {
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IdRef {
|
||||
fn drop(&mut self) {
|
||||
if self.0 != nil {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.0, release];
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for IdRef {
|
||||
type Target = id;
|
||||
fn deref(&self) -> &id {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for IdRef {
|
||||
fn clone(&self) -> IdRef {
|
||||
IdRef::retain(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! trace_scope {
|
||||
($s:literal) => {
|
||||
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
|
||||
|
@ -124,57 +61,3 @@ pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
|
|||
CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat,
|
||||
)
|
||||
}
|
||||
|
||||
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
|
||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // In case we want to use this function in the future
|
||||
pub unsafe fn app_name() -> Option<id> {
|
||||
let bundle: id = msg_send![class!(NSBundle), mainBundle];
|
||||
let dict: id = msg_send![bundle, infoDictionary];
|
||||
let key = ns_string_id_ref("CFBundleName");
|
||||
let app_name: id = msg_send![dict, objectForKey:*key];
|
||||
if app_name != nil {
|
||||
Some(app_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub unsafe fn open_emoji_picker() {
|
||||
let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil];
|
||||
}
|
||||
|
||||
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
|
||||
let current_style_mask = window.styleMask();
|
||||
if on {
|
||||
window.setStyleMask_(current_style_mask | mask);
|
||||
} else {
|
||||
window.setStyleMask_(current_style_mask & (!mask));
|
||||
}
|
||||
|
||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
||||
window.makeFirstResponder_(view);
|
||||
}
|
||||
|
||||
/// For invalid utf8 sequences potentially returned by `UTF8String`,
|
||||
/// it behaves identically to `String::from_utf8_lossy`
|
||||
///
|
||||
/// Safety: Assumes that `string` is an instance of `NSAttributedString` or `NSString`
|
||||
pub unsafe fn id_to_string_lossy(string: id) -> String {
|
||||
let has_attr = msg_send![string, isKindOfClass: class!(NSAttributedString)];
|
||||
let characters = if has_attr {
|
||||
// This is a *mut NSAttributedString
|
||||
msg_send![string, string]
|
||||
} else {
|
||||
// This is already a *mut NSString
|
||||
string
|
||||
};
|
||||
let utf8_sequence =
|
||||
std::slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len());
|
||||
String::from_utf8_lossy(utf8_sequence).into_owned()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -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…
Reference in a new issue