From 7c2e1300c26a0634ad505ce72b90eb6dc2fdcac7 Mon Sep 17 00:00:00 2001 From: Francesca Plebani Date: Wed, 24 Apr 2019 20:58:26 -0600 Subject: [PATCH 1/6] Squashed commit of the following: commit 5f4aa9f01a719eef98c6d894801c20ee8f96d30f Author: Francesca Plebani Date: Fri Dec 21 17:14:14 2018 -0500 Protect against reentrancy (messily) commit b75073a5b2a8d65ab8806a00ffee390752255c8c Author: Francesca Plebani Date: Fri Dec 21 15:15:27 2018 -0500 Send resize events immediately commit 8e9fc01bd6b404f59488b130413f48e4e5f89b0d Author: Francesca Plebani Date: Fri Dec 21 16:07:43 2018 -0500 Don't use struct for window delegate commit c6853b0c4a8fe357f463604bb879dc1be424860e Author: Francesca Plebani Date: Wed Dec 19 21:17:48 2018 -0500 Split up util commit 262c46b148413130fa239099f1151c1f1bd5c13c Author: Francesca Plebani Date: Wed Dec 19 20:55:00 2018 -0500 Use dispatch crate commit 63152c2f475794d1a36a5b3687c777664d7d5613 Author: Francesca Plebani Date: Wed Dec 19 20:29:13 2018 -0500 RedrawRequested commit 27e475c7c78b059fd9b5e8350cd26756eecdfc94 Author: Francesca Plebani Date: Wed Dec 19 19:24:44 2018 -0500 User events commit 157418d7dedace9c571e977d98ea92464c3188b2 Author: Francesca Plebani Date: Tue Dec 18 22:38:05 2018 -0500 Moved out cursor loading commit b4925641c973979a38743202b4269efe09ac43b4 Author: Francesca Plebani Date: Tue Dec 18 21:32:12 2018 -0500 Fixed a bunch of threading issues commit 4aef63dfb78dfaf38c83cb0e88d4ea9d8d0578a6 Author: Francesca Plebani Date: Mon Dec 17 13:54:59 2018 -0500 Wait works commit 72ed426c695df5dc410902263bd74188059b8ddd Author: Francesca Plebani Date: Fri Dec 14 20:49:10 2018 -0500 Fixed drag and dropg commit 658209f4a20acd536218f41a01fb8cbbebc705e0 Author: Francesca Plebani Date: Fri Dec 14 20:42:42 2018 -0500 Made mutexes finer for less deadlock risk commit 8e6b9866084690da900c4d058e412cab8ebb30c4 Author: Francesca Plebani Date: Fri Dec 14 16:45:06 2018 -0500 Dump (encapsulate) everything into AppState commit d2dc83df15939d89301e2cff0ffa2d98c48b406f Author: Francesca Plebani Date: Thu Dec 13 17:36:47 2018 -0500 All window events work! commit 7c7fcc98872b3c35bd7767b5c6235a74bc105e06 Author: Francesca Plebani Date: Wed Dec 12 17:11:09 2018 -0500 Very rough usage of CFRunLoop commit 3c7a52ff4df683b5b7e1751e4051ec445a818774 Author: Francesca Plebani Date: Tue Dec 11 15:45:23 2018 -0500 Fixed deadlocks commit b74c7fe1bcd173e9b0c0e004956c257e805bc2a2 Author: Francesca Plebani Date: Mon Dec 10 18:59:46 2018 -0500 Fix keyDown deadlock commit 3798f9c1a4bef2a3d1552f846b26efc31b1bbb6c Author: Francesca Plebani Date: Mon Dec 10 18:44:40 2018 -0500 It builds! commit 8c8620214357714c5cd0b3beefda6704512e3f64 Author: Francesca Plebani Date: Fri Dec 7 21:09:55 2018 -0500 Horribly broken so far commit 8269ed2a9270e5ec5b14f80fd21d1e0e6f51be29 Author: Osspial Date: Mon Nov 19 23:51:20 2018 -0500 Fix crash with runner refcell not getting dropped commit 54ce6a21a0722e408ae49c74f5008005fc1e4cbf Author: Osspial Date: Sun Nov 18 19:12:45 2018 -0500 Fix buffered events not getting dispatched commit 2c18b804df66f49f93cfe722a679d6c5e01d8cb1 Author: Osspial Date: Sun Nov 18 18:51:24 2018 -0500 Fix thread executor not executing closure when called from non-loop thread commit 5a3a5e2293cec3e566c4aac344ae7eaa343608b5 Author: Osspial Date: Thu Nov 15 22:43:59 2018 -0500 Fix some deadlocks that could occur when changing window state commit 2a3cefd8c5df1c06127b05651cbdf5e3d9e3a6d3 Author: Osspial Date: Thu Nov 15 16:45:17 2018 -0500 Document and implement Debug for EventLoopWindowTarget commit fa46825a289ca0587dc97f9c00dea5516fb4925a Author: Osspial Date: Thu Nov 15 16:40:48 2018 -0500 Replace &EventLoop in callback with &EventLoopWindowTarget commit 9f36a7a68e1dc379cf9091213dae2c3586d3e473 Author: Osspial Date: Wed Nov 14 21:28:38 2018 -0500 Fix freeze when setting decorations commit d9c3daca9b459e02ef614568fe803a723965fe8d Author: Osspial Date: Fri Nov 9 20:41:15 2018 -0500 Fix 1.24.1 build commit 5289d22372046bac403a279c3641737c0cfc46d2 Author: Osspial Date: Fri Nov 9 00:00:27 2018 -0500 Remove serde implementations from ControlFlow commit 92ac3d6ac7915923c22c380cc3a74c5f3830708e Author: Osspial Date: Thu Nov 8 23:46:41 2018 -0500 Remove crossbeam dependency and make drop events work again commit 8299eb2f03773a34079c61fc8adb51405aafc467 Author: Osspial Date: Thu Sep 13 22:39:40 2018 -0400 Fix crash when running in release mode commit bb6ab1bb6e9595e90f1915fdde7e23904f2ba594 Author: Osspial Date: Sun Sep 9 14:28:16 2018 -0400 Fix unreachable panic after setting ControlFlow to Poll during some RedrawRequested events. commit 5068ff4ee152bfe93c9190235f02d001202feb88 Author: Osspial Date: Sun Sep 9 14:14:28 2018 -0400 Improve clarity/fix typos in docs commit 8ed575ff4a4f0961bb2e784bda1ae109c6bd37b7 Author: Osspial Date: Sun Sep 9 00:19:53 2018 -0400 Update send test and errors that broke some examples/APIs commit bf7bfa82ebb5d6ae110ce0492c124ef462945f85 Author: Osspial Date: Wed Sep 5 22:36:05 2018 -0400 Fix resize lag when waiting in some situations commit 70722cc4c322e3e599b3a03bce5058a5d433970b Author: Osspial Date: Wed Sep 5 19:58:52 2018 -1100 When SendEvent is called during event closure, buffer events commit 53370924b25da15ddd172173150b228065324864 Author: Osspial Date: Sun Aug 26 21:55:51 2018 -0400 Improve WaitUntil timer precision commit a654400e730400c2e3584be2f47153043b5b7efe Author: Osspial Date: Thu Aug 23 21:06:19 2018 -0400 Add CHANGELOG entry commit deb7d379b7c04e61d6d50ff655eccac0ad692e44 Author: Osspial Date: Thu Aug 23 20:19:56 2018 -0400 Rename MonitorId to MonitorHandle commit 8d8d9b7cd1386c99c40023d86e17d10c3fd6652f Author: Osspial Date: Thu Aug 23 20:16:52 2018 -0400 Change instances of "events_loop" to "event_loop" commit 0f344087630ae252c9c8f453864e684a1a5405b1 Author: Osspial Date: Thu Aug 23 20:13:53 2018 -0400 Improve docs for run and run_return commit fba41f7a7ed8585cbb658b6d0b2f34f75482cb3d Author: Osspial Date: Thu Aug 23 19:09:53 2018 -0400 Small changes to examples commit 42e8a0d2cf77af79da082fff7cd29cc8f52d99df Author: Osspial Date: Thu Aug 23 19:09:19 2018 -0400 Improve documentation commit 4377680a44ea86dad52954f90bc7d8ad7ed0b4bf Author: Osspial Date: Wed Aug 22 23:01:36 2018 -0400 Re-organize into module structure commit f20fac99f6ac57c51603a92d792fd4f665feb7f6 Author: Osspial Date: Wed Aug 22 22:07:39 2018 -0400 Add platform::desktop module with EventLoopExt::run_return commit dad24d086aaaff60e557efc4f41d1ae7e3c71738 Author: Osspial Date: Wed Aug 22 18:03:41 2018 -0400 Rename os to platform, add Ext trait postfixes commit 7df59c60a06008226f6455619e7242ed0156ed8d Author: Osspial Date: Wed Aug 22 17:59:36 2018 -0400 Rename platform to platform_impl commit 99c0f84a9fc771c9c96099232de3716ddf27ca80 Author: Osspial Date: Wed Aug 22 17:55:27 2018 -0400 Add request_redraw commit a0fef1a5fad9b5d5da59fff191c7d9c398ea9e01 Author: Osspial Date: Mon Aug 20 01:47:11 2018 -0400 Fully invert windows control flow so win32 calls into winit's callback commit 2c607ff87f8fbcad8aa9dc3783b3298c014dd177 Author: Osspial Date: Sun Aug 19 13:44:22 2018 -0400 Add ability to send custom user events commit a0b2bb36953f018ff782cef8fc86c6db9343095d Author: Osspial Date: Fri Aug 17 17:49:46 2018 -0400 Add StartCause::Init support, timer example commit 02f922f003f56215b92b8feeb9148ad2dd181fc2 Author: Osspial Date: Fri Aug 17 17:31:04 2018 -0400 Implement new ControlFlow and associated events commit 8b8a7675ec67e15a0f8f69db0bdeb79bee0ac20d Author: Osspial Date: Fri Jul 13 01:48:26 2018 -0400 Replace windows Mutex with parking_lot Mutex commit 9feada206f6b9fb1e9da118be6b77dfc217ace8d Author: Osspial Date: Fri Jul 13 01:39:53 2018 -0400 Update run_forever to hijack thread commit 2e83bac99cc264cd2723cb182feea84a0a15e08d Author: Osspial Date: Thu Jul 12 23:43:58 2018 -0400 Remove second thread from win32 backend commit 64b8a9c6a50362d10c074077a1e37b057f3e3c81 Author: Osspial Date: Thu Jul 12 22:13:07 2018 -0400 Rename WindowEvent::Refresh to WindowEvent::Redraw commit 529c08555fd0b709a23d486211d28fbd0980fc94 Author: Osspial Date: Thu Jul 12 22:04:38 2018 -0400 Rename EventsLoop and associated types to EventLoop Signed-off-by: Hal Gentz Co-authored-by: Hal Gentz --- CHANGELOG.md | 37 ++ Cargo.toml | 4 +- examples/multithreaded.rs | 115 ++++++ src/platform/macos.rs | 5 +- src/platform_impl/macos/app.rs | 88 ++++ src/platform_impl/macos/app_delegate.rs | 101 +++++ src/platform_impl/macos/app_state.rs | 310 ++++++++++++++ src/platform_impl/macos/event.rs | 202 +++++++++ src/platform_impl/macos/ffi.rs | 1 + src/platform_impl/macos/monitor.rs | 20 +- src/platform_impl/macos/observer.rs | 259 ++++++++++++ src/platform_impl/macos/util/async.rs | 327 +++++++++++++++ src/platform_impl/macos/util/cursor.rs | 2 +- src/platform_impl/macos/window_delegate.rs | 460 +++++++++++++++++++++ src/util.rs | 12 + 15 files changed, 1931 insertions(+), 12 deletions(-) create mode 100644 examples/multithreaded.rs create mode 100644 src/platform_impl/macos/app.rs create mode 100644 src/platform_impl/macos/app_delegate.rs create mode 100644 src/platform_impl/macos/app_state.rs create mode 100644 src/platform_impl/macos/event.rs create mode 100644 src/platform_impl/macos/observer.rs create mode 100644 src/platform_impl/macos/util/async.rs create mode 100644 src/platform_impl/macos/window_delegate.rs create mode 100644 src/util.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index bf53744b65c..7f57c4d9263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,41 @@ # Unreleased +- Changes below are considered **breaking**. +- Change all occurrences of `EventsLoop` to `EventLoop`. +- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. +- `os` module changes: + - Renamed to `platform`. + - All traits now have platform-specific suffixes. + - Exposes new `desktop` module on Windows, Mac, and Linux. +- Changes to event loop types: + - `EventLoopProxy::wakeup` has been removed in favor of `send_event`. + - **Major:** New `run` method drives winit event loop. + - Returns `!` to ensure API behaves identically across all supported platforms. + - This allows `emscripten` implementation to work without lying about the API. + - `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`. + - Is read after `EventsCleared` is processed. + - `Wait` waits until new events are available. + - `WaitUntil` waits until either new events are available or the provided time has been reached. + - `Poll` instantly resumes the event loop. + - `Exit` aborts the event loop. + - Takes a closure that implements `'static + FnMut(Event, &EventLoop, &mut ControlFlow)`. + - `&EventLoop` is provided to allow new `Window`s to be created. + - **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method. + - Behaves identically to `run`, but returns control flow to the calling context can take non-`'static` closures. + - `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`. +- Changes to events: + - Remove `Event::Awakened` in favor of `Event::UserEvent(T)`. + - Can be sent with `EventLoopProxy::send_event`. + - Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`. + - `RedrawRequested` can be sent by the user with the `Window::request_redraw` method. + - `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`. + - **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`. + - `NewEvents` is emitted when new events are ready to be processed by event loop. + - `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop). + - `EventsCleared` is emitted when all available events have been processed. + - Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop). + - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. +- Rename `MonitorId` to `MonitorHandle`. +- Removed `serde` implementations from `ControlFlow`. - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. diff --git a/Cargo.toml b/Cargo.toml index 370b8ff6a5b..3ed03fd6b17 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ serde = { version = "1", optional = true, features = ["serde_derive"] } [dev-dependencies] image = "0.21" +env_logger = "0.5" [target.'cfg(target_os = "android")'.dependencies.android_glue] version = "0.2" @@ -29,10 +30,11 @@ version = "0.2" objc = "0.2.3" [target.'cfg(target_os = "macos")'.dependencies] -objc = "0.2.3" cocoa = "0.18.4" core-foundation = "0.6" core-graphics = "0.17.3" +dispatch = "0.1.4" +objc = "0.2.3" [target.'cfg(target_os = "windows")'.dependencies] backtrace = "0.3" diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs new file mode 100644 index 00000000000..f5907ca3efd --- /dev/null +++ b/examples/multithreaded.rs @@ -0,0 +1,115 @@ +extern crate env_logger; +extern crate winit; + +use std::{collections::HashMap, sync::mpsc, thread, time::Duration}; + +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, window::{MouseCursor, WindowBuilder}, +}; + +const WINDOW_COUNT: usize = 3; +const WINDOW_SIZE: (u32, u32) = (600, 400); + +fn main() { + env_logger::init(); + let event_loop = EventLoop::new(); + let mut window_senders = HashMap::with_capacity(WINDOW_COUNT); + for _ in 0..WINDOW_COUNT { + let window = WindowBuilder::new() + .with_dimensions(WINDOW_SIZE.into()) + .build(&event_loop) + .unwrap(); + let (tx, rx) = mpsc::channel(); + window_senders.insert(window.id(), tx); + thread::spawn(move || { + while let Ok(event) = rx.recv() { + match event { + WindowEvent::KeyboardInput { input: KeyboardInput { + state: ElementState::Released, + virtual_keycode: Some(key), + modifiers, + .. + }, .. } => { + window.set_title(&format!("{:?}", key)); + let state = !modifiers.shift; + use self::VirtualKeyCode::*; + match key { + A => window.set_always_on_top(state), + C => window.set_cursor(match state { + true => MouseCursor::Progress, + false => MouseCursor::Default, + }), + D => window.set_decorations(!state), + F => window.set_fullscreen(match state { + true => Some(window.get_current_monitor()), + false => None, + }), + G => window.grab_cursor(state).unwrap(), + H => window.hide_cursor(state), + I => { + println!("Info:"); + println!("-> position : {:?}", window.get_position()); + println!("-> inner_position : {:?}", window.get_inner_position()); + println!("-> outer_size : {:?}", window.get_outer_size()); + println!("-> inner_size : {:?}", window.get_inner_size()); + }, + L => window.set_min_dimensions(match state { + true => Some(WINDOW_SIZE.into()), + false => None, + }), + M => window.set_maximized(state), + P => window.set_position({ + let mut position = window.get_position().unwrap(); + let sign = if state { 1.0 } else { -1.0 }; + position.x += 10.0 * sign; + position.y += 10.0 * sign; + position + }), + Q => window.request_redraw(), + R => window.set_resizable(state), + S => window.set_inner_size(match state { + true => (WINDOW_SIZE.0 + 100, WINDOW_SIZE.1 + 100), + false => WINDOW_SIZE, + }.into()), + W => window.set_cursor_position(( + WINDOW_SIZE.0 as i32 / 2, + WINDOW_SIZE.1 as i32 / 2, + ).into()).unwrap(), + Z => { + window.hide(); + thread::sleep(Duration::from_secs(1)); + window.show(); + }, + _ => (), + } + }, + _ => (), + } + } + }); + } + event_loop.run(move |event, _event_loop, control_flow| { + *control_flow = match !window_senders.is_empty() { + true => ControlFlow::Wait, + false => ControlFlow::Exit, + }; + match event { + Event::WindowEvent { event, window_id } => { + match event { + WindowEvent::CloseRequested + | WindowEvent::Destroyed + | WindowEvent::KeyboardInput { input: KeyboardInput { + virtual_keycode: Some(VirtualKeyCode::Escape), + .. }, .. } => { + window_senders.remove(&window_id); + }, + _ => if let Some(tx) = window_senders.get(&window_id) { + tx.send(event).unwrap(); + }, + } + } + _ => (), + } + }) +} diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 8997e3b2657..cca09f91491 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -1,7 +1,10 @@ #![cfg(target_os = "macos")] use std::os::raw::c_void; -use {LogicalSize, MonitorHandle, Window, WindowBuilder}; + +use crate::dpi::LogicalSize; +use crate::monitor::MonitorHandle; +use crate::window::{Window, WindowBuilder}; /// Additional methods on `Window` that are specific to MacOS. pub trait WindowExtMacOS { diff --git a/src/platform_impl/macos/app.rs b/src/platform_impl/macos/app.rs new file mode 100644 index 00000000000..2699eedc2b0 --- /dev/null +++ b/src/platform_impl/macos/app.rs @@ -0,0 +1,88 @@ +use std::collections::VecDeque; + +use cocoa::{appkit::{self, NSEvent}, base::id}; +use objc::{declare::ClassDecl, runtime::{Class, Object, Sel}}; + +use event::{DeviceEvent, Event}; +use platform_impl::platform::{app_state::AppState, DEVICE_ID, util}; + +pub struct AppClass(pub *const Class); +unsafe impl Send for AppClass {} +unsafe impl Sync for AppClass {} + +lazy_static! { + pub static ref APP_CLASS: AppClass = unsafe { + let superclass = class!(NSApplication); + let mut decl = ClassDecl::new("WinitApp", superclass).unwrap(); + + decl.add_method( + sel!(sendEvent:), + send_event as extern fn(&Object, Sel, id), + ); + + AppClass(decl.register()) + }; +} + +// Normally, holding Cmd + any key never sends us a `keyUp` event for that key. +// 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) +extern fn send_event(this: &Object, _sel: Sel, 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![this, keyWindow]; + let _: () = msg_send![key_window, sendEvent:event]; + } else { + maybe_dispatch_device_event(event); + let superclass = util::superclass(this); + let _: () = msg_send![super(this, superclass), sendEvent:event]; + } + } +} + +unsafe fn maybe_dispatch_device_event(event: id) { + let event_type = event.eventType(); + match event_type { + appkit::NSMouseMoved | + appkit::NSLeftMouseDragged | + appkit::NSOtherMouseDragged | + appkit::NSRightMouseDragged => { + let mut events = VecDeque::with_capacity(3); + + let delta_x = event.deltaX() as f64; + let delta_y = event.deltaY() as f64; + + if delta_x != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { axis: 0, value: delta_x }, + }); + } + + if delta_y != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::Motion { axis: 1, value: delta_y }, + }); + } + + if delta_x != 0.0 || delta_y != 0.0 { + events.push_back(Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseMotion { delta: (delta_x, delta_y) }, + }); + } + + AppState::queue_events(events); + }, + _ => (), + } +} diff --git a/src/platform_impl/macos/app_delegate.rs b/src/platform_impl/macos/app_delegate.rs new file mode 100644 index 00000000000..8e4ab19dcd2 --- /dev/null +++ b/src/platform_impl/macos/app_delegate.rs @@ -0,0 +1,101 @@ +use cocoa::base::id; +use objc::{runtime::{Class, Object, Sel, BOOL, YES}, declare::ClassDecl}; + +use platform_impl::platform::app_state::AppState; + +pub struct AppDelegateClass(pub *const Class); +unsafe impl Send for AppDelegateClass {} +unsafe impl Sync for AppDelegateClass {} + +lazy_static! { + pub static ref APP_DELEGATE_CLASS: AppDelegateClass = unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap(); + + decl.add_method( + sel!(applicationDidFinishLaunching:), + did_finish_launching as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(applicationDidBecomeActive:), + did_become_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillResignActive:), + will_resign_active as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillEnterForeground:), + will_enter_foreground as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationDidEnterBackground:), + did_enter_background as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(applicationWillTerminate:), + will_terminate as extern fn(&Object, Sel, id), + ); + + AppDelegateClass(decl.register()) + }; +} + +extern fn did_finish_launching(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `didFinishLaunching`"); + AppState::launched(); + trace!("Completed `didFinishLaunching`"); + YES +} + +extern fn did_become_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `didBecomeActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(false)) + }*/ + trace!("Completed `didBecomeActive`"); +} + +extern fn will_resign_active(_: &Object, _: Sel, _: id) { + trace!("Triggered `willResignActive`"); + /*unsafe { + HANDLER.lock().unwrap().handle_nonuser_event(Event::Suspended(true)) + }*/ + trace!("Completed `willResignActive`"); +} + +extern fn will_enter_foreground(_: &Object, _: Sel, _: id) { + trace!("Triggered `willEnterForeground`"); + trace!("Completed `willEnterForeground`"); +} + +extern fn did_enter_background(_: &Object, _: Sel, _: id) { + trace!("Triggered `didEnterBackground`"); + trace!("Completed `didEnterBackground`"); +} + +extern fn will_terminate(_: &Object, _: Sel, _: id) { + trace!("Triggered `willTerminate`"); + /*unsafe { + let app: id = msg_send![class!(UIApplication), sharedApplication]; + let windows: id = msg_send![app, windows]; + let windows_enum: id = msg_send![windows, objectEnumerator]; + let mut events = Vec::new(); + loop { + let window: id = msg_send![windows_enum, nextObject]; + if window == nil { + break + } + let is_winit_window: BOOL = msg_send![window, isKindOfClass:class!(WinitUIWindow)]; + if is_winit_window == YES { + events.push(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Destroyed, + }); + } + } + HANDLER.lock().unwrap().handle_nonuser_events(events); + HANDLER.lock().unwrap().terminated(); + }*/ + trace!("Completed `willTerminate`"); +} diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs new file mode 100644 index 00000000000..593aa765f51 --- /dev/null +++ b/src/platform_impl/macos/app_state.rs @@ -0,0 +1,310 @@ +use std::{ + collections::VecDeque, fmt::{self, Debug, Formatter}, + hint::unreachable_unchecked, mem, + sync::{atomic::{AtomicBool, Ordering}, Mutex, MutexGuard}, time::Instant, +}; + +use cocoa::{appkit::NSApp, base::nil}; + +use { + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, + window::WindowId, +}; +use platform_impl::platform::{observer::EventLoopWaker, util::Never}; + +lazy_static! { + static ref HANDLER: Handler = Default::default(); +} + +impl Event { + fn userify(self) -> Event { + self.map_nonuser_event() + // `Never` can't be constructed, so the `UserEvent` variant can't + // be present here. + .unwrap_or_else(|_| unsafe { unreachable_unchecked() }) + } +} + +pub trait EventHandler: Debug { + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow); + fn handle_user_events(&mut self, control_flow: &mut ControlFlow); +} + +struct EventLoopHandler { + callback: F, + will_exit: bool, + window_target: RootWindowTarget, +} + +impl Debug for EventLoopHandler { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.debug_struct("EventLoopHandler") + .field("window_target", &self.window_target) + .finish() + } +} + +impl EventHandler for EventLoopHandler +where + F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + T: 'static, +{ + fn handle_nonuser_event(&mut self, event: Event, control_flow: &mut ControlFlow) { + (self.callback)( + event.userify(), + &self.window_target, + control_flow, + ); + self.will_exit |= *control_flow == ControlFlow::Exit; + if self.will_exit { + *control_flow = ControlFlow::Exit; + } + } + + fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { + let mut will_exit = self.will_exit; + for event in self.window_target.inner.receiver.try_iter() { + (self.callback)( + Event::UserEvent(event), + &self.window_target, + control_flow, + ); + will_exit |= *control_flow == ControlFlow::Exit; + if will_exit { + *control_flow = ControlFlow::Exit; + } + } + self.will_exit = will_exit; + } +} + +#[derive(Default)] +struct Handler { + ready: AtomicBool, + in_callback: AtomicBool, + control_flow: Mutex, + control_flow_prev: Mutex, + start_time: Mutex>, + callback: Mutex>>, + pending_events: Mutex>>, + deferred_events: Mutex>>, + pending_redraw: Mutex>, + waker: Mutex, +} + +unsafe impl Send for Handler {} +unsafe impl Sync for Handler {} + +impl Handler { + fn events<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { + self.pending_events.lock().unwrap() + } + + fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque>> { + self.deferred_events.lock().unwrap() + } + + fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec> { + self.pending_redraw.lock().unwrap() + } + + fn waker<'a>(&'a self) -> MutexGuard<'a, EventLoopWaker> { + self.waker.lock().unwrap() + } + + fn is_ready(&self) -> bool { + self.ready.load(Ordering::Acquire) + } + + fn set_ready(&self) { + self.ready.store(true, Ordering::Release); + } + + fn should_exit(&self) -> bool { + *self.control_flow.lock().unwrap() == ControlFlow::Exit + } + + fn get_control_flow_and_update_prev(&self) -> ControlFlow { + let control_flow = self.control_flow.lock().unwrap(); + *self.control_flow_prev.lock().unwrap() = *control_flow; + *control_flow + } + + fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) { + let old = *self.control_flow_prev.lock().unwrap(); + let new = *self.control_flow.lock().unwrap(); + (old, new) + } + + fn get_start_time(&self) -> Option { + *self.start_time.lock().unwrap() + } + + fn update_start_time(&self) { + *self.start_time.lock().unwrap() = Some(Instant::now()); + } + + fn take_events(&self) -> VecDeque> { + mem::replace(&mut *self.events(), Default::default()) + } + + fn take_deferred(&self) -> VecDeque> { + mem::replace(&mut *self.deferred(), Default::default()) + } + + fn should_redraw(&self) -> Vec { + mem::replace(&mut *self.redraw(), Default::default()) + } + + fn get_in_callback(&self) -> bool { + self.in_callback.load(Ordering::Acquire) + } + + fn set_in_callback(&self, in_callback: bool) { + self.in_callback.store(in_callback, Ordering::Release); + } + + fn handle_nonuser_event(&self, event: Event) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + callback.handle_nonuser_event( + event, + &mut *self.control_flow.lock().unwrap(), + ); + } + } + + fn handle_user_events(&self) { + if let Some(ref mut callback) = *self.callback.lock().unwrap() { + callback.handle_user_events( + &mut *self.control_flow.lock().unwrap(), + ); + } + } +} + +pub enum AppState {} + +impl AppState { + pub fn set_callback(callback: F, window_target: RootWindowTarget) + where + F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), + T: 'static, + { + *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { + callback, + will_exit: false, + window_target, + })); + } + + pub fn exit() { + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::LoopDestroyed); + HANDLER.set_in_callback(false); + } + + pub fn launched() { + HANDLER.set_ready(); + HANDLER.waker().start(); + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init)); + HANDLER.set_in_callback(false); + } + + pub fn wakeup() { + if !HANDLER.is_ready() { return } + let start = HANDLER.get_start_time().unwrap(); + let cause = match HANDLER.get_control_flow_and_update_prev() { + ControlFlow::Poll => StartCause::Poll, + ControlFlow::Wait => StartCause::WaitCancelled { + start, + requested_resume: None, + }, + ControlFlow::WaitUntil(requested_resume) => { + if Instant::now() >= requested_resume { + StartCause::ResumeTimeReached { + start, + requested_resume, + } + } else { + StartCause::WaitCancelled { + start, + requested_resume: Some(requested_resume), + } + } + }, + ControlFlow::Exit => StartCause::Poll,//panic!("unexpected `ControlFlow::Exit`"), + }; + HANDLER.set_in_callback(true); + HANDLER.handle_nonuser_event(Event::NewEvents(cause)); + HANDLER.set_in_callback(false); + } + + // This is called from multiple threads at present + pub fn queue_redraw(window_id: WindowId) { + let mut pending_redraw = HANDLER.redraw(); + if !pending_redraw.contains(&window_id) { + pending_redraw.push(window_id); + } + } + + pub fn queue_event(event: Event) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Event queued from different thread: {:#?}", event); + } + HANDLER.events().push_back(event); + } + + pub fn queue_events(mut events: VecDeque>) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Events queued from different thread: {:#?}", events); + } + HANDLER.events().append(&mut events); + } + + pub fn send_event_immediately(event: Event) { + if !unsafe { msg_send![class!(NSThread), isMainThread] } { + panic!("Event sent from different thread: {:#?}", event); + } + HANDLER.deferred().push_back(event); + if !HANDLER.get_in_callback() { + HANDLER.set_in_callback(true); + for event in HANDLER.take_deferred() { + HANDLER.handle_nonuser_event(event); + } + HANDLER.set_in_callback(false); + } + } + + pub fn cleared() { + if !HANDLER.is_ready() { return } + if !HANDLER.get_in_callback() { + HANDLER.set_in_callback(true); + HANDLER.handle_user_events(); + for event in HANDLER.take_events() { + HANDLER.handle_nonuser_event(event); + } + for window_id in HANDLER.should_redraw() { + HANDLER.handle_nonuser_event(Event::WindowEvent { + window_id, + event: WindowEvent::RedrawRequested, + }); + } + HANDLER.handle_nonuser_event(Event::EventsCleared); + HANDLER.set_in_callback(false); + } + if HANDLER.should_exit() { + let _: () = unsafe { msg_send![NSApp(), stop:nil] }; + return + } + HANDLER.update_start_time(); + match HANDLER.get_old_and_new_control_flow() { + (ControlFlow::Exit, _) | (_, ControlFlow::Exit) => unreachable!(), + (old, new) if old == new => (), + (_, ControlFlow::Wait) => HANDLER.waker().stop(), + (_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant), + (_, ControlFlow::Poll) => HANDLER.waker().start(), + } + } +} diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs new file mode 100644 index 00000000000..10cd876f81d --- /dev/null +++ b/src/platform_impl/macos/event.rs @@ -0,0 +1,202 @@ +use std::os::raw::c_ushort; + +use cocoa::{appkit::{NSEvent, NSEventModifierFlags}, base::id}; + +use event::{ + ElementState, KeyboardInput, + ModifiersState, VirtualKeyCode, WindowEvent, +}; +use platform_impl::platform::DEVICE_ID; + +pub fn to_virtual_keycode(scancode: c_ushort) -> Option { + Some(match scancode { + 0x00 => VirtualKeyCode::A, + 0x01 => VirtualKeyCode::S, + 0x02 => VirtualKeyCode::D, + 0x03 => VirtualKeyCode::F, + 0x04 => VirtualKeyCode::H, + 0x05 => VirtualKeyCode::G, + 0x06 => VirtualKeyCode::Z, + 0x07 => VirtualKeyCode::X, + 0x08 => VirtualKeyCode::C, + 0x09 => VirtualKeyCode::V, + //0x0a => World 1, + 0x0b => VirtualKeyCode::B, + 0x0c => VirtualKeyCode::Q, + 0x0d => VirtualKeyCode::W, + 0x0e => VirtualKeyCode::E, + 0x0f => VirtualKeyCode::R, + 0x10 => VirtualKeyCode::Y, + 0x11 => VirtualKeyCode::T, + 0x12 => VirtualKeyCode::Key1, + 0x13 => VirtualKeyCode::Key2, + 0x14 => VirtualKeyCode::Key3, + 0x15 => VirtualKeyCode::Key4, + 0x16 => VirtualKeyCode::Key6, + 0x17 => VirtualKeyCode::Key5, + 0x18 => VirtualKeyCode::Equals, + 0x19 => VirtualKeyCode::Key9, + 0x1a => VirtualKeyCode::Key7, + 0x1b => VirtualKeyCode::Minus, + 0x1c => VirtualKeyCode::Key8, + 0x1d => VirtualKeyCode::Key0, + 0x1e => VirtualKeyCode::RBracket, + 0x1f => VirtualKeyCode::O, + 0x20 => VirtualKeyCode::U, + 0x21 => VirtualKeyCode::LBracket, + 0x22 => VirtualKeyCode::I, + 0x23 => VirtualKeyCode::P, + 0x24 => VirtualKeyCode::Return, + 0x25 => VirtualKeyCode::L, + 0x26 => VirtualKeyCode::J, + 0x27 => VirtualKeyCode::Apostrophe, + 0x28 => VirtualKeyCode::K, + 0x29 => VirtualKeyCode::Semicolon, + 0x2a => VirtualKeyCode::Backslash, + 0x2b => VirtualKeyCode::Comma, + 0x2c => VirtualKeyCode::Slash, + 0x2d => VirtualKeyCode::N, + 0x2e => VirtualKeyCode::M, + 0x2f => VirtualKeyCode::Period, + 0x30 => VirtualKeyCode::Tab, + 0x31 => VirtualKeyCode::Space, + 0x32 => VirtualKeyCode::Grave, + 0x33 => VirtualKeyCode::Back, + //0x34 => unkown, + 0x35 => VirtualKeyCode::Escape, + 0x36 => VirtualKeyCode::RWin, + 0x37 => VirtualKeyCode::LWin, + 0x38 => VirtualKeyCode::LShift, + //0x39 => Caps lock, + 0x3a => VirtualKeyCode::LAlt, + 0x3b => VirtualKeyCode::LControl, + 0x3c => VirtualKeyCode::RShift, + 0x3d => VirtualKeyCode::RAlt, + 0x3e => VirtualKeyCode::RControl, + //0x3f => Fn key, + 0x40 => VirtualKeyCode::F17, + 0x41 => VirtualKeyCode::Decimal, + //0x42 -> unkown, + 0x43 => VirtualKeyCode::Multiply, + //0x44 => unkown, + 0x45 => VirtualKeyCode::Add, + //0x46 => unkown, + 0x47 => VirtualKeyCode::Numlock, + //0x48 => KeypadClear, + 0x49 => VirtualKeyCode::VolumeUp, + 0x4a => VirtualKeyCode::VolumeDown, + 0x4b => VirtualKeyCode::Divide, + 0x4c => VirtualKeyCode::NumpadEnter, + //0x4d => unkown, + 0x4e => VirtualKeyCode::Subtract, + 0x4f => VirtualKeyCode::F18, + 0x50 => VirtualKeyCode::F19, + 0x51 => VirtualKeyCode::NumpadEquals, + 0x52 => VirtualKeyCode::Numpad0, + 0x53 => VirtualKeyCode::Numpad1, + 0x54 => VirtualKeyCode::Numpad2, + 0x55 => VirtualKeyCode::Numpad3, + 0x56 => VirtualKeyCode::Numpad4, + 0x57 => VirtualKeyCode::Numpad5, + 0x58 => VirtualKeyCode::Numpad6, + 0x59 => VirtualKeyCode::Numpad7, + 0x5a => VirtualKeyCode::F20, + 0x5b => VirtualKeyCode::Numpad8, + 0x5c => VirtualKeyCode::Numpad9, + //0x5d => unkown, + //0x5e => unkown, + //0x5f => unkown, + 0x60 => VirtualKeyCode::F5, + 0x61 => VirtualKeyCode::F6, + 0x62 => VirtualKeyCode::F7, + 0x63 => VirtualKeyCode::F3, + 0x64 => VirtualKeyCode::F8, + 0x65 => VirtualKeyCode::F9, + //0x66 => unkown, + 0x67 => VirtualKeyCode::F11, + //0x68 => unkown, + 0x69 => VirtualKeyCode::F13, + 0x6a => VirtualKeyCode::F16, + 0x6b => VirtualKeyCode::F14, + //0x6c => unkown, + 0x6d => VirtualKeyCode::F10, + //0x6e => unkown, + 0x6f => VirtualKeyCode::F12, + //0x70 => unkown, + 0x71 => VirtualKeyCode::F15, + 0x72 => VirtualKeyCode::Insert, + 0x73 => VirtualKeyCode::Home, + 0x74 => VirtualKeyCode::PageUp, + 0x75 => VirtualKeyCode::Delete, + 0x76 => VirtualKeyCode::F4, + 0x77 => VirtualKeyCode::End, + 0x78 => VirtualKeyCode::F2, + 0x79 => VirtualKeyCode::PageDown, + 0x7a => VirtualKeyCode::F1, + 0x7b => VirtualKeyCode::Left, + 0x7c => VirtualKeyCode::Right, + 0x7d => VirtualKeyCode::Down, + 0x7e => VirtualKeyCode::Up, + //0x7f => unkown, + + 0xa => VirtualKeyCode::Caret, + _ => return None, + }) +} + +// While F1-F20 have scancodes we can match on, we have to check against UTF-16 +// constants for the rest. +// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ +pub fn check_function_keys(string: &Option) -> Option { + string + .as_ref() + .and_then(|string| string.encode_utf16().next()) + .and_then(|character| match character { + 0xf718 => Some(VirtualKeyCode::F21), + 0xf719 => Some(VirtualKeyCode::F22), + 0xf71a => Some(VirtualKeyCode::F23), + 0xf71b => Some(VirtualKeyCode::F24), + _ => None, + }) +} + +pub fn event_mods(event: id) -> ModifiersState { + let flags = unsafe { + NSEvent::modifierFlags(event) + }; + ModifiersState { + shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask), + ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask), + alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask), + logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask), + } +} + +pub unsafe fn modifier_event( + ns_event: id, + keymask: NSEventModifierFlags, + was_key_pressed: bool, +) -> Option { + if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask) + || was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) { + let state = if was_key_pressed { + ElementState::Released + } else { + ElementState::Pressed + }; + let keycode = NSEvent::keyCode(ns_event); + let scancode = keycode as u32; + let virtual_keycode = to_virtual_keycode(keycode); + Some(WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state, + scancode, + virtual_keycode, + modifiers: event_mods(ns_event), + }, + }) + } else { + None + } +} diff --git a/src/platform_impl/macos/ffi.rs b/src/platform_impl/macos/ffi.rs index 31c9ed149e7..d199ebb6a8d 100644 --- a/src/platform_impl/macos/ffi.rs +++ b/src/platform_impl/macos/ffi.rs @@ -95,6 +95,7 @@ pub const kCGDesktopIconWindowLevelKey: NSInteger = 18; pub const kCGCursorWindowLevelKey: NSInteger = 19; pub const kCGNumberOfWindowLevelKeys: NSInteger = 20; +#[derive(Debug, Clone, Copy)] pub enum NSWindowLevel { NSNormalWindowLevel = kCGBaseWindowLevelKey as _, NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _, diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index 7b3e848832e..b9fb053672f 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -1,23 +1,20 @@ -use std::collections::VecDeque; -use std::fmt; +use std::{collections::VecDeque, fmt}; -use cocoa::appkit::NSScreen; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSString, NSUInteger}; +use cocoa::{appkit::NSScreen, base::{id, nil}, foundation::{NSString, NSUInteger}}; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use {PhysicalPosition, PhysicalSize}; +use crate::dpi::{PhysicalPosition, PhysicalSize}; use super::EventLoop; use super::window::{IdRef, Window2}; #[derive(Clone, PartialEq)] pub struct MonitorHandle(CGDirectDisplayID); -fn get_available_monitors() -> VecDeque { +pub fn get_available_monitors() -> VecDeque { if let Ok(displays) = CGDisplay::active_displays() { let mut monitors = VecDeque::with_capacity(displays.len()); - for d in displays { - monitors.push_back(MonitorHandle(d)); + for display in displays { + monitors.push_back(MonitorHandle(display)); } monitors } else { @@ -61,6 +58,7 @@ impl Window2 { impl fmt::Debug for MonitorHandle { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: Do this using the proper fmt API #[derive(Debug)] struct MonitorHandle { name: Option, @@ -83,6 +81,10 @@ impl fmt::Debug for MonitorHandle { } impl MonitorHandle { + pub fn new(id: CGDirectDisplayID) -> Self { + MonitorHandle(id) + } + pub fn get_name(&self) -> Option { let MonitorHandle(display_id) = *self; let screen_num = CGDisplay::new(display_id).model_number(); diff --git a/src/platform_impl/macos/observer.rs b/src/platform_impl/macos/observer.rs new file mode 100644 index 00000000000..79b4c997b73 --- /dev/null +++ b/src/platform_impl/macos/observer.rs @@ -0,0 +1,259 @@ +use std::{self, ptr, os::raw::*, time::Instant}; + +use platform_impl::platform::app_state::AppState; + +#[link(name = "CoreFoundation", kind = "framework")] +extern { + pub static kCFRunLoopDefaultMode: CFRunLoopMode; + pub static kCFRunLoopCommonModes: CFRunLoopMode; + + pub fn CFRunLoopGetMain() -> CFRunLoopRef; + pub fn CFRunLoopWakeUp(rl: CFRunLoopRef); + + pub fn CFRunLoopObserverCreate( + allocator: CFAllocatorRef, + activities: CFOptionFlags, + repeats: Boolean, + order: CFIndex, + callout: CFRunLoopObserverCallBack, + context: *mut CFRunLoopObserverContext, + ) -> CFRunLoopObserverRef; + pub fn CFRunLoopAddObserver( + rl: CFRunLoopRef, + observer: CFRunLoopObserverRef, + mode: CFRunLoopMode, + ); + + pub fn CFRunLoopTimerCreate( + allocator: CFAllocatorRef, + fireDate: CFAbsoluteTime, + interval: CFTimeInterval, + flags: CFOptionFlags, + order: CFIndex, + callout: CFRunLoopTimerCallBack, + context: *mut CFRunLoopTimerContext, + ) -> CFRunLoopTimerRef; + pub fn CFRunLoopAddTimer( + rl: CFRunLoopRef, + timer: CFRunLoopTimerRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopTimerSetNextFireDate( + timer: CFRunLoopTimerRef, + fireDate: CFAbsoluteTime, + ); + pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef); + + pub fn CFRunLoopSourceCreate( + allocator: CFAllocatorRef, + order: CFIndex, + context: *mut CFRunLoopSourceContext, + ) -> CFRunLoopSourceRef; + pub fn CFRunLoopAddSource( + rl: CFRunLoopRef, + source: CFRunLoopSourceRef, + mode: CFRunLoopMode, + ); + pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef); + pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef); + + pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime; + pub fn CFRelease(cftype: *const c_void); +} + +pub type Boolean = u8; +const FALSE: Boolean = 0; +const TRUE: Boolean = 1; + +pub enum CFAllocator {} +pub type CFAllocatorRef = *mut CFAllocator; +pub enum CFRunLoop {} +pub type CFRunLoopRef = *mut CFRunLoop; +pub type CFRunLoopMode = CFStringRef; +pub enum CFRunLoopObserver {} +pub type CFRunLoopObserverRef = *mut CFRunLoopObserver; +pub enum CFRunLoopTimer {} +pub type CFRunLoopTimerRef = *mut CFRunLoopTimer; +pub enum CFRunLoopSource {} +pub type CFRunLoopSourceRef = *mut CFRunLoopSource; +pub enum CFString {} +pub type CFStringRef = *const CFString; + +pub type CFHashCode = c_ulong; +pub type CFIndex = c_long; +pub type CFOptionFlags = c_ulong; +pub type CFRunLoopActivity = CFOptionFlags; + +pub type CFAbsoluteTime = CFTimeInterval; +pub type CFTimeInterval = f64; + +pub const kCFRunLoopEntry: CFRunLoopActivity = 0; +pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5; +pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6; +pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7; + +pub type CFRunLoopObserverCallBack = extern "C" fn( + observer: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + info: *mut c_void, +); +pub type CFRunLoopTimerCallBack = extern "C" fn( + timer: CFRunLoopTimerRef, + info: *mut c_void +); + +pub enum CFRunLoopObserverContext {} +pub enum CFRunLoopTimerContext {} + +#[repr(C)] +pub struct CFRunLoopSourceContext { + pub version: CFIndex, + pub info: *mut c_void, + pub retain: extern "C" fn(*const c_void) -> *const c_void, + pub release: extern "C" fn(*const c_void), + pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, + pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, + pub hash: extern "C" fn(*const c_void) -> CFHashCode, + pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), + pub perform: extern "C" fn(*mut c_void), +} + +// begin is queued with the highest priority to ensure it is processed before other observers +extern fn control_flow_begin_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopAfterWaiting => { + //trace!("Triggered `CFRunLoopAfterWaiting`"); + AppState::wakeup(); + //trace!("Completed `CFRunLoopAfterWaiting`"); + }, + kCFRunLoopEntry => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } +} + +// end is queued with the lowest priority to ensure it is processed after other observers +// without that, LoopDestroyed would get sent after EventsCleared +extern fn control_flow_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, +) { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => { + //trace!("Triggered `CFRunLoopBeforeWaiting`"); + AppState::cleared(); + //trace!("Completed `CFRunLoopBeforeWaiting`"); + }, + kCFRunLoopExit => (),//unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } +} + +struct RunLoop(CFRunLoopRef); + +impl RunLoop { + unsafe fn get() -> Self { + RunLoop(CFRunLoopGetMain()) + } + + unsafe fn add_observer( + &self, + flags: CFOptionFlags, + priority: CFIndex, + handler: CFRunLoopObserverCallBack, + ) { + let observer = CFRunLoopObserverCreate( + ptr::null_mut(), + flags, + TRUE, // Indicates we want this to run repeatedly + priority, // The lower the value, the sooner this will run + handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode); + } +} + +pub fn setup_control_flow_observers() { + unsafe { + let run_loop = RunLoop::get(); + run_loop.add_observer( + kCFRunLoopEntry | kCFRunLoopAfterWaiting, + CFIndex::min_value(), + control_flow_begin_handler, + ); + run_loop.add_observer( + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + CFIndex::max_value(), + control_flow_end_handler, + ); + } +} + + +pub struct EventLoopWaker { + timer: CFRunLoopTimerRef, +} + +impl Drop for EventLoopWaker { + fn drop(&mut self) { + unsafe { + CFRunLoopTimerInvalidate(self.timer); + CFRelease(self.timer as _); + } + } +} + +impl Default for EventLoopWaker { + fn default() -> EventLoopWaker { + extern fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {} + unsafe { + // create a timer with a 1µs interval (1ns does not work) to mimic polling. + // it is initially setup with a first fire time really far into the + // future, but that gets changed to fire immediatley in did_finish_launching + let timer = CFRunLoopTimerCreate( + ptr::null_mut(), + std::f64::MAX, + 0.000_000_1, + 0, + 0, + wakeup_main_loop, + ptr::null_mut(), + ); + CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes); + EventLoopWaker { timer } + } + } +} + +impl EventLoopWaker { + pub fn stop(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) } + } + + pub fn start(&mut self) { + unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) } + } + + pub fn start_at(&mut self, instant: Instant) { + let now = Instant::now(); + if now >= instant { + self.start(); + } else { + unsafe { + let current = CFAbsoluteTimeGetCurrent(); + let duration = instant - now; + let fsecs = duration.subsec_nanos() as f64 / 1_000_000_000.0 + + duration.as_secs() as f64; + CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs) + } + } + } +} diff --git a/src/platform_impl/macos/util/async.rs b/src/platform_impl/macos/util/async.rs new file mode 100644 index 00000000000..4ececc0396a --- /dev/null +++ b/src/platform_impl/macos/util/async.rs @@ -0,0 +1,327 @@ +use std::{os::raw::c_void, sync::{Mutex, Weak}}; + +use cocoa::{ + appkit::{CGFloat, NSWindow, NSWindowStyleMask}, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSPoint, NSSize}, +}; +use dispatch::ffi::{dispatch_async_f, dispatch_get_main_queue, dispatch_sync_f}; + +use dpi::LogicalSize; +use platform_impl::platform::{ffi, window::SharedState}; + +unsafe fn set_style_mask(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + nswindow.setStyleMask_(mask); + // If we don't do this, key handling will break + // (at least until the window is clicked again/etc.) + nswindow.makeFirstResponder_(nsview); +} + +struct SetStyleMaskData { + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, +} +impl SetStyleMaskData { + fn new_ptr( + nswindow: id, + nsview: id, + mask: NSWindowStyleMask, + ) -> *mut Self { + Box::into_raw(Box::new(SetStyleMaskData { nswindow, nsview, mask })) + } +} +extern fn set_style_mask_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetStyleMaskData; + { + let context = &*context_ptr; + set_style_mask(context.nswindow, context.nsview, context.mask); + } + Box::from_raw(context_ptr); + } +} +// 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(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_style_mask_callback, + ); +} +pub unsafe fn set_style_mask_sync(nswindow: id, nsview: id, mask: NSWindowStyleMask) { + let context = SetStyleMaskData::new_ptr(nswindow, nsview, mask); + dispatch_sync_f( + dispatch_get_main_queue(), + context as *mut _, + set_style_mask_callback, + ); +} + +struct SetContentSizeData { + nswindow: id, + size: LogicalSize, +} +impl SetContentSizeData { + fn new_ptr( + nswindow: id, + size: LogicalSize, + ) -> *mut Self { + Box::into_raw(Box::new(SetContentSizeData { nswindow, size })) + } +} +extern fn set_content_size_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetContentSizeData; + { + let context = &*context_ptr; + NSWindow::setContentSize_( + context.nswindow, + NSSize::new( + context.size.width as CGFloat, + context.size.height as CGFloat, + ), + ); + } + Box::from_raw(context_ptr); + } +} +// `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(nswindow: id, size: LogicalSize) { + let context = SetContentSizeData::new_ptr(nswindow, size); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_content_size_callback, + ); +} + +struct SetFrameTopLeftPointData { + nswindow: id, + point: NSPoint, +} +impl SetFrameTopLeftPointData { + fn new_ptr( + nswindow: id, + point: NSPoint, + ) -> *mut Self { + Box::into_raw(Box::new(SetFrameTopLeftPointData { nswindow, point })) + } +} +extern fn set_frame_top_left_point_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetFrameTopLeftPointData; + { + let context = &*context_ptr; + NSWindow::setFrameTopLeftPoint_(context.nswindow, context.point); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy +// to log errors. +pub unsafe fn set_frame_top_left_point_async(nswindow: id, point: NSPoint) { + let context = SetFrameTopLeftPointData::new_ptr(nswindow, point); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_frame_top_left_point_callback, + ); +} + +struct SetLevelData { + nswindow: id, + level: ffi::NSWindowLevel, +} +impl SetLevelData { + fn new_ptr( + nswindow: id, + level: ffi::NSWindowLevel, + ) -> *mut Self { + Box::into_raw(Box::new(SetLevelData { nswindow, level })) + } +} +extern fn set_level_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut SetLevelData; + { + let context = &*context_ptr; + context.nswindow.setLevel_(context.level as _); + } + Box::from_raw(context_ptr); + } +} +// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently. +pub unsafe fn set_level_async(nswindow: id, level: ffi::NSWindowLevel) { + let context = SetLevelData::new_ptr(nswindow, level); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + set_level_callback, + ); +} + +struct ToggleFullScreenData { + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +} +impl ToggleFullScreenData { + fn new_ptr( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, + ) -> *mut Self { + Box::into_raw(Box::new(ToggleFullScreenData { + nswindow, + nsview, + not_fullscreen, + shared_state, + })) + } +} +extern fn toggle_full_screen_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut ToggleFullScreenData; + { + let context = &*context_ptr; + + // `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 context.not_fullscreen { + let curr_mask = context.nswindow.styleMask(); + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + if !curr_mask.contains(required) { + set_style_mask(context.nswindow, context.nsview, required); + if let Some(shared_state) = context.shared_state.upgrade() { + trace!("Locked shared state in `toggle_full_screen_callback`"); + let mut shared_state_lock = shared_state.lock().unwrap(); + (*shared_state_lock).saved_style = Some(curr_mask); + trace!("Unlocked shared state in `toggle_full_screen_callback`"); + } + } + } + + context.nswindow.toggleFullScreen_(nil); + } + Box::from_raw(context_ptr); + } +} +// `toggleFullScreen` is thread-safe, but our additional logic to account for +// window styles isn't. +pub unsafe fn toggle_full_screen_async( + nswindow: id, + nsview: id, + not_fullscreen: bool, + shared_state: Weak>, +) { + let context = ToggleFullScreenData::new_ptr( + nswindow, + nsview, + not_fullscreen, + shared_state, + ); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + toggle_full_screen_callback, + ); +} + +struct OrderOutData { + nswindow: id, +} +impl OrderOutData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(OrderOutData { nswindow })) + } +} +extern fn order_out_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut OrderOutData; + { + let context = &*context_ptr; + context.nswindow.orderOut_(nil); + } + Box::from_raw(context_ptr); + } +} +// `orderOut:` isn't thread-safe. Calling it from another thread actually works, +// but with an odd delay. +pub unsafe fn order_out_async(nswindow: id) { + let context = OrderOutData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + order_out_callback, + ); +} + +struct MakeKeyAndOrderFrontData { + nswindow: id, +} +impl MakeKeyAndOrderFrontData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(MakeKeyAndOrderFrontData { nswindow })) + } +} +extern fn make_key_and_order_front_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut MakeKeyAndOrderFrontData; + { + let context = &*context_ptr; + context.nswindow.makeKeyAndOrderFront_(nil); + } + Box::from_raw(context_ptr); + } +} +// `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(nswindow: id) { + let context = MakeKeyAndOrderFrontData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + make_key_and_order_front_callback, + ); +} + +struct CloseData { + nswindow: id, +} +impl CloseData { + fn new_ptr(nswindow: id) -> *mut Self { + Box::into_raw(Box::new(CloseData { nswindow })) + } +} +extern fn close_callback(context: *mut c_void) { + unsafe { + let context_ptr = context as *mut CloseData; + { + let context = &*context_ptr; + let pool = NSAutoreleasePool::new(nil); + context.nswindow.close(); + pool.drain(); + } + Box::from_raw(context_ptr); + } +} +// `close:` is thread-safe, but we want the event to be triggered from the main +// thread. Though, it's a good idea to look into that more... +pub unsafe fn close_async(nswindow: id) { + let context = CloseData::new_ptr(nswindow); + dispatch_async_f( + dispatch_get_main_queue(), + context as *mut _, + close_callback, + ); +} diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index e7815d786b2..e741188af04 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -5,7 +5,7 @@ use cocoa::{ use objc::runtime::Sel; use super::IntoOption; -use MouseCursor; +use window::MouseCursor; pub enum Cursor { Native(&'static str), diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs new file mode 100644 index 00000000000..83defb3f2bf --- /dev/null +++ b/src/platform_impl/macos/window_delegate.rs @@ -0,0 +1,460 @@ +use std::{f64, os::raw::c_void, sync::{Arc, Weak}}; + +use cocoa::{ + appkit::{self, NSView, NSWindow}, base::{id, nil}, + foundation::NSAutoreleasePool, +}; +use objc::{runtime::{Class, Object, Sel, BOOL, YES, NO}, declare::ClassDecl}; + +use {dpi::LogicalSize, event::{Event, WindowEvent}, window::WindowId}; +use platform_impl::platform::{ + app_state::AppState, util::{self, IdRef}, + window::{get_window_id, UnownedWindow}, +}; + +pub struct WindowDelegateState { + nswindow: IdRef, // never changes + nsview: IdRef, // never changes + + window: Weak, + + // 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_dpi_factor: f64, +} + +impl WindowDelegateState { + pub fn new( + window: &Arc, + initial_fullscreen: bool, + ) -> Self { + let dpi_factor = window.get_hidpi_factor(); + + let mut delegate_state = WindowDelegateState { + nswindow: window.nswindow.clone(), + nsview: window.nsview.clone(), + window: Arc::downgrade(&window), + initial_fullscreen, + previous_position: None, + previous_dpi_factor: dpi_factor, + }; + + if dpi_factor != 1.0 { + delegate_state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + delegate_state.emit_resize_event(); + } + + delegate_state + } + + fn with_window(&mut self, callback: F) -> Option + where F: FnOnce(&UnownedWindow) -> T + { + self.window + .upgrade() + .map(|ref window| callback(window)) + } + + pub fn emit_event(&mut self, event: WindowEvent) { + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(*self.nswindow)), + event, + }; + AppState::queue_event(event); + } + + pub fn emit_resize_event(&mut self) { + let rect = unsafe { NSView::frame(*self.nsview) }; + let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); + let event = Event::WindowEvent { + window_id: WindowId(get_window_id(*self.nswindow)), + event: WindowEvent::Resized(size), + }; + AppState::send_event_immediately(event); + } + + fn emit_move_event(&mut self) { + let rect = unsafe { NSWindow::frame(*self.nswindow) }; + 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)); + self.emit_event(WindowEvent::Moved((x, y).into())); + } + } +} + +pub fn new_delegate(window: &Arc, 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![WINDOW_DELEGATE_CLASS.0, alloc]; + IdRef::new(msg_send![delegate, initWithWinit:state_ptr]) + } +} + +struct WindowDelegateClass(*const Class); +unsafe impl Send for WindowDelegateClass {} +unsafe impl Sync for WindowDelegateClass {} + +lazy_static! { + static ref WINDOW_DELEGATE_CLASS: WindowDelegateClass = unsafe { + let superclass = class!(NSResponder); + let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); + + decl.add_method( + sel!(dealloc), + dealloc as extern fn(&Object, Sel), + ); + decl.add_method( + sel!(initWithWinit:), + init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id, + ); + + decl.add_method( + sel!(windowShouldClose:), + window_should_close as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(windowWillClose:), + window_will_close as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResize:), + window_did_resize as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidMove:), + window_did_move as extern fn(&Object, Sel, id)); + decl.add_method( + sel!(windowDidChangeScreen:), + window_did_change_screen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidChangeBackingProperties:), + window_did_change_backing_properties as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidBecomeKey:), + window_did_become_key as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResignKey:), + window_did_resign_key as extern fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(draggingEntered:), + dragging_entered as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(prepareForDragOperation:), + prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(performDragOperation:), + perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(concludeDragOperation:), + conclude_drag_operation as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(draggingExited:), + dragging_exited as extern fn(&Object, Sel, id), + ); + + decl.add_method( + sel!(windowDidEnterFullScreen:), + window_did_enter_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowWillEnterFullScreen:), + window_will_enter_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidExitFullScreen:), + window_did_exit_fullscreen as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidFailToEnterFullScreen:), + window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id), + ); + + decl.add_ivar::<*mut c_void>("winitState"); + WindowDelegateClass(decl.register()) + }; +} + +// This function is definitely unsafe, but labeling that would increase +// boilerplate and wouldn't really clarify anything... +fn with_state T, T>(this: &Object, callback: F) { + let state_ptr = unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + &mut *(state_ptr as *mut WindowDelegateState) + }; + callback(state_ptr); +} + +extern fn dealloc(this: &Object, _sel: Sel) { + with_state(this, |state| unsafe { + Box::from_raw(state as *mut WindowDelegateState); + }); +} + +extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id { + unsafe { + let this: id = msg_send![this, init]; + if this != nil { + (*this).set_ivar("winitState", state); + with_state(&*this, |state| { + let () = msg_send![*state.nswindow, setDelegate:this]; + }); + } + this + } +} + +extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `windowShouldClose:`"); + with_state(this, |state| state.emit_event(WindowEvent::CloseRequested)); + trace!("Completed `windowShouldClose:`"); + NO +} + +extern fn window_will_close(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillClose:`"); + with_state(this, |state| unsafe { + // `setDelegate:` retains the previous value and then autoreleases it + let pool = NSAutoreleasePool::new(nil); + // Since El Capitan, we need to be careful that delegate methods can't + // be called after the window closes. + let () = msg_send![*state.nswindow, setDelegate:nil]; + pool.drain(); + state.emit_event(WindowEvent::Destroyed); + }); + trace!("Completed `windowWillClose:`"); +} + +extern fn window_did_resize(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResize:`"); + with_state(this, |state| { + state.emit_resize_event(); + state.emit_move_event(); + }); + trace!("Completed `windowDidResize:`"); +} + +// This won't be triggered if the move was part of a resize. +extern fn window_did_move(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidMove:`"); + with_state(this, |state| { + state.emit_move_event(); + }); + trace!("Completed `windowDidMove:`"); +} + +extern fn window_did_change_screen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidChangeScreen:`"); + with_state(this, |state| { + let dpi_factor = unsafe { + NSWindow::backingScaleFactor(*state.nswindow) + } as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + state.emit_resize_event(); + } + }); + trace!("Completed `windowDidChangeScreen:`"); +} + +// This will always be called before `window_did_change_screen`. +extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) { + trace!("Triggered `windowDidChangeBackingProperties:`"); + with_state(this, |state| { + let dpi_factor = unsafe { + NSWindow::backingScaleFactor(*state.nswindow) + } as f64; + if state.previous_dpi_factor != dpi_factor { + state.previous_dpi_factor = dpi_factor; + state.emit_event(WindowEvent::HiDpiFactorChanged(dpi_factor)); + state.emit_resize_event(); + } + }); + trace!("Completed `windowDidChangeBackingProperties:`"); +} + +extern fn window_did_become_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidBecomeKey:`"); + with_state(this, |state| { + // TODO: center the cursor if the window had mouse grab when it + // lost focus + state.emit_event(WindowEvent::Focused(true)); + }); + trace!("Completed `windowDidBecomeKey:`"); +} + +extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidResignKey:`"); + with_state(this, |state| { + state.emit_event(WindowEvent::Focused(false)); + }); + trace!("Completed `windowDidResignKey:`"); +} + +/// Invoked when the dragged image enters destination bounds or frame +extern fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `draggingEntered:`"); + + use cocoa::appkit::NSPasteboard; + use cocoa::foundation::NSFastEnumeration; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + 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(); + + with_state(this, |state| { + state.emit_event(WindowEvent::HoveredFile(PathBuf::from(path))); + }); + } + }; + + trace!("Completed `draggingEntered:`"); + YES +} + +/// Invoked when the image is released +extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { + trace!("Triggered `prepareForDragOperation:`"); + trace!("Completed `prepareForDragOperation:`"); + YES +} + +/// Invoked after the released image has been removed from the screen +extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { + trace!("Triggered `performDragOperation:`"); + + use cocoa::appkit::NSPasteboard; + use cocoa::foundation::NSFastEnumeration; + use std::path::PathBuf; + + let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; + let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; + + 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(); + + with_state(this, |state| { + state.emit_event(WindowEvent::DroppedFile(PathBuf::from(path))); + }); + } + }; + + trace!("Completed `performDragOperation:`"); + YES +} + +/// Invoked when the dragging operation is complete +extern fn conclude_drag_operation(_: &Object, _: Sel, _: id) { + trace!("Triggered `concludeDragOperation:`"); + trace!("Completed `concludeDragOperation:`"); +} + +/// Invoked when the dragging operation is cancelled +extern fn dragging_exited(this: &Object, _: Sel, _: id) { + trace!("Triggered `draggingExited:`"); + with_state(this, |state| state.emit_event(WindowEvent::HoveredFileCancelled)); + trace!("Completed `draggingExited:`"); +} + +/// Invoked when before enter fullscreen +extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowWillEnterFullscreen:`"); + with_state(this, |state| state.with_window(|window| { + trace!("Locked shared state in `window_will_enter_fullscreen`"); + window.shared_state.lock().unwrap().maximized = window.is_zoomed(); + trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + })); + trace!("Completed `windowWillEnterFullscreen:`"); +} + +/// Invoked when entered fullscreen +extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidEnterFullscreen:`"); + with_state(this, |state| { + state.with_window(|window| { + let monitor = window.get_current_monitor(); + trace!("Locked shared state in `window_did_enter_fullscreen`"); + window.shared_state.lock().unwrap().fullscreen = Some(monitor); + trace!("Unlocked shared state in `window_will_enter_fullscreen`"); + }); + state.initial_fullscreen = false; + }); + trace!("Completed `windowDidEnterFullscreen:`"); +} + +/// Invoked when exited fullscreen +extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidExitFullscreen:`"); + with_state(this, |state| state.with_window(|window| { + window.restore_state_from_fullscreen(); + })); + trace!("Completed `windowDidExitFullscreen:`"); +} + +/// Invoked when fail to enter fullscreen +/// +/// When this window launch from a fullscreen app (e.g. launch from VS Code +/// terminal), it creates a new virtual destkop and a transition animation. +/// This animation takes one second and cannot be disable without +/// elevated privileges. In this animation time, all toggleFullscreen events +/// will be failed. In this implementation, we will try again by using +/// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. +/// It should be fine as we only do this at initialzation (i.e with_fullscreen +/// was set). +/// +/// From Apple doc: +/// In some cases, the transition to enter full-screen mode can fail, +/// due to being in the midst of handling some other animation or user gesture. +/// 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. +extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { + trace!("Triggered `windowDidFailToEnterFullscreen:`"); + with_state(this, |state| { + if state.initial_fullscreen { + let _: () = unsafe { msg_send![*state.nswindow, + performSelector:sel!(toggleFullScreen:) + withObject:nil + afterDelay: 0.5 + ] }; + } else { + state.with_window(|window| window.restore_state_from_fullscreen()); + } + }); + trace!("Completed `windowDidFailToEnterFullscreen:`"); +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 00000000000..6d50fb13885 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,12 @@ +use std::ops::BitAnd; + +// Replace with `!` once stable +#[derive(Debug)] +pub enum Never {} + +pub fn has_flag(bitset: T, flag: T) -> bool +where T: + Copy + PartialEq + BitAnd +{ + bitset & flag == flag +} From 6c81f2a517d4e2d5ba2ff3eddca030bce972cb2a Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Wed, 24 Apr 2019 23:05:57 -0600 Subject: [PATCH 2/6] Francesca's macos changes Also backports https://github.com/rust-windowing/winit/commit/bfbcab3a010667e74fa3cc07ac85bd4c85491e65#diff-1d95fe39cdbaa708c975380a16c314cb --- src/lib.rs | 2 + .../linux/x11/util/window_property.rs | 2 - src/platform_impl/macos/app_state.rs | 2 +- src/platform_impl/macos/event_loop.rs | 882 ++------- src/platform_impl/macos/mod.rs | 70 +- src/platform_impl/macos/monitor.rs | 37 +- src/platform_impl/macos/util/cursor.rs | 42 +- src/platform_impl/macos/util/mod.rs | 116 +- src/platform_impl/macos/view.rs | 686 +++++-- src/platform_impl/macos/window.rs | 1616 ++++++----------- src/util.rs | 12 - 11 files changed, 1323 insertions(+), 2144 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d9e12e8dc9c..4936241ee56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,8 @@ extern crate objc; #[cfg(target_os = "macos")] extern crate cocoa; #[cfg(target_os = "macos")] +extern crate dispatch; +#[cfg(target_os = "macos")] extern crate core_foundation; #[cfg(target_os = "macos")] extern crate core_graphics; diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index 47d984e6178..81d65ebacd6 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -1,5 +1,3 @@ -use std; - use super::*; pub type Cardinal = c_long; diff --git a/src/platform_impl/macos/app_state.rs b/src/platform_impl/macos/app_state.rs index 593aa765f51..cc9d4b229fa 100644 --- a/src/platform_impl/macos/app_state.rs +++ b/src/platform_impl/macos/app_state.rs @@ -64,7 +64,7 @@ where fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { let mut will_exit = self.will_exit; - for event in self.window_target.inner.receiver.try_iter() { + for event in self.window_target.p.receiver.try_iter() { (self.callback)( Event::UserEvent(event), &self.window_target, diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 42ad8cb4337..23c2102ae28 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -1,812 +1,144 @@ -use {ControlFlow, EventLoopClosed}; -use cocoa::{self, appkit, foundation}; -use cocoa::appkit::{NSApplication, NSEvent, NSEventMask, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}; -use events::{self, ElementState, Event, TouchPhase, WindowEvent, DeviceEvent, ModifiersState, KeyboardInput}; -use std::collections::VecDeque; -use std::sync::{Arc, Mutex, Weak}; -use super::window::Window2; -use std; -use std::os::raw::*; -use super::DeviceId; - -pub struct EventLoop { - modifiers: Modifiers, - pub shared: Arc, -} - -// State shared between the `EventLoop` and its registered windows. -pub struct Shared { - pub windows: Mutex>>, - pub pending_events: Mutex>, - // The user event callback given via either of the `poll_events` or `run_forever` methods. - // - // We store the user's callback here so that it may be accessed by each of the window delegate - // callbacks (e.g. resize, close, etc) for the duration of a call to either of the - // `poll_events` or `run_forever` methods. - // - // This is *only* `Some` for the duration of a call to either of these methods and will be - // `None` otherwise. - user_callback: UserCallback, +use std::{ + collections::VecDeque, mem, os::raw::c_void, process, ptr, sync::mpsc, marker::PhantomData +}; + +use cocoa::{appkit::NSApp, base::{id, nil}, foundation::NSAutoreleasePool}; + +use { + event::Event, + event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget}, +}; +use platform_impl::platform::{ + app::APP_CLASS, app_delegate::APP_DELEGATE_CLASS, + app_state::AppState, monitor::{self, MonitorHandle}, + observer::*, util::IdRef, +}; + +pub struct EventLoopWindowTarget { + pub sender: mpsc::Sender, // this is only here to be cloned elsewhere + pub receiver: mpsc::Receiver, } -#[derive(Clone)] -pub struct Proxy {} - -struct Modifiers { - shift_pressed: bool, - ctrl_pressed: bool, - win_pressed: bool, - alt_pressed: bool, +impl Default for EventLoopWindowTarget { + fn default() -> Self { + let (sender, receiver) = mpsc::channel(); + EventLoopWindowTarget { sender, receiver } + } } -// Wrapping the user callback in a type allows us to: -// -// - ensure the callback pointer is never accidentally cloned -// - ensure that only the `EventLoop` can `store` and `drop` the callback pointer -// - Share access to the user callback with the NSWindow callbacks. -pub struct UserCallback { - mutex: Mutex>, +pub struct EventLoop { + window_target: RootWindowTarget, + _delegate: IdRef, } - -impl Shared { - +impl EventLoop { pub fn new() -> Self { - Shared { - windows: Mutex::new(Vec::new()), - pending_events: Mutex::new(VecDeque::new()), - user_callback: UserCallback { mutex: Mutex::new(None) }, - } - } - - fn call_user_callback_with_pending_events(&self) { - loop { - let event = match self.pending_events.lock().unwrap().pop_front() { - Some(event) => event, - None => return, - }; - unsafe { - self.user_callback.call_with_event(event); - } - } - } - - // Calls the user callback if one exists. - // - // Otherwise, stores the event in the `pending_events` queue. - // - // This is necessary for the case when `WindowDelegate` callbacks are triggered during a call - // to the user's callback. - pub fn call_user_callback_with_event_or_store_in_pending(&self, event: Event) { - if self.user_callback.mutex.lock().unwrap().is_some() { - unsafe { - self.user_callback.call_with_event(event); + let delegate = unsafe { + if !msg_send![class!(NSThread), isMainThread] { + panic!("On macOS, `EventLoop` must be created on the main thread!"); } - } else { - self.pending_events.lock().unwrap().push_back(event); - } - } - // Removes the window with the given `Id` from the `windows` list. - // - // This is called in response to `windowWillClose`. - pub fn find_and_remove_window(&self, id: super::window::Id) { - if let Ok(mut windows) = self.windows.lock() { - windows.retain(|w| match w.upgrade() { - Some(w) => w.id() != id, - None => false, - }); - } - } - -} - - -impl Modifiers { - pub fn new() -> Self { - Modifiers { - shift_pressed: false, - ctrl_pressed: false, - win_pressed: false, - alt_pressed: false, + // 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![APP_CLASS.0, sharedApplication]; + + let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]); + let pool = NSAutoreleasePool::new(nil); + let _: () = msg_send![app, setDelegate:*delegate]; + let _: () = msg_send![pool, drain]; + delegate + }; + setup_control_flow_observers(); + EventLoop { + window_target: RootWindowTarget { + p: Default::default(), + _marker: PhantomData, + }, + _delegate: delegate, } } -} - - -impl UserCallback { - // Here we store user's `callback` behind the mutex so that they may be safely shared between - // each of the window delegates. - // - // In order to make sure that the pointer is always valid, we must manually guarantee that it - // is dropped before the callback itself is dropped. Thus, this should *only* be called at the - // beginning of a call to `poll_events` and `run_forever`, both of which *must* drop the - // callback at the end of their scope using the `drop` method. - fn store(&self, callback: &mut F) - where F: FnMut(Event) - { - let trait_object = callback as &mut FnMut(Event); - let trait_object_ptr = trait_object as *const FnMut(Event) as *mut FnMut(Event); - *self.mutex.lock().unwrap() = Some(trait_object_ptr); + #[inline] + pub fn get_available_monitors(&self) -> VecDeque { + monitor::get_available_monitors() } - // Emits the given event via the user-given callback. - // - // This is unsafe as it requires dereferencing the pointer to the user-given callback. We - // guarantee this is safe by ensuring the `UserCallback` never lives longer than the user-given - // callback. - // - // Note that the callback may not always be `Some`. This is because some `NSWindowDelegate` - // callbacks can be triggered by means other than `NSApp().sendEvent`. For example, if a window - // is destroyed or created during a call to the user's callback, the `WindowDelegate` methods - // may be called with `windowShouldClose` or `windowDidResignKey`. - unsafe fn call_with_event(&self, event: Event) { - let callback = match self.mutex.lock().unwrap().take() { - Some(callback) => callback, - None => return, - }; - (*callback)(event); - *self.mutex.lock().unwrap() = Some(callback); + #[inline] + pub fn get_primary_monitor(&self) -> MonitorHandle { + monitor::get_primary_monitor() } - // Used to drop the user callback pointer at the end of the `poll_events` and `run_forever` - // methods. This is done to enforce our guarantee that the top callback will never live longer - // than the call to either `poll_events` or `run_forever` to which it was given. - fn drop(&self) { - self.mutex.lock().unwrap().take(); + pub fn window_target(&self) -> &RootWindowTarget { + &self.window_target } -} - - -impl EventLoop { - - pub fn new() -> Self { - // Mark this thread as the main thread of the Cocoa event system. - // - // This must be done before any worker threads get a chance to call it - // (e.g., via `EventLoopProxy::wakeup()`), causing a wrong thread to be - // marked as the main thread. - unsafe { appkit::NSApp(); } - - EventLoop { - shared: Arc::new(Shared::new()), - modifiers: Modifiers::new(), - } - } - - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event), + pub fn run(self, callback: F) -> ! + where F: 'static + FnMut(Event, &RootWindowTarget, &mut ControlFlow), { unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Events can only be polled from the main thread on macOS"); - } - } - - self.shared.user_callback.store(&mut callback); - - // Loop as long as we have pending events to return. - loop { - unsafe { - // First, yield all pending events. - self.shared.call_user_callback_with_pending_events(); - - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - - // Poll for the next event, returning `nil` if there are none. - let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(), - foundation::NSDate::distantPast(cocoa::base::nil), - foundation::NSDefaultRunLoopMode, - cocoa::base::YES); - - let event = self.ns_event_to_event(ns_event); - - let _: () = msg_send![pool, release]; - - match event { - // Call the user's callback. - Some(event) => self.shared.user_callback.call_with_event(event), - None => break, - } - } + let _pool = NSAutoreleasePool::new(nil); + let app = NSApp(); + assert_ne!(app, nil); + AppState::set_callback(callback, self.window_target); + let _: () = msg_send![app, run]; + AppState::exit(); + process::exit(0) } - - self.shared.user_callback.drop(); } - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow + pub fn run_return(&mut self, _callback: F) + where F: FnMut(Event, &RootWindowTarget, &mut ControlFlow), { - unsafe { - if !msg_send![class!(NSThread), isMainThread] { - panic!("Events can only be polled from the main thread on macOS"); - } - } - - // Track whether or not control flow has changed. - let control_flow = std::cell::Cell::new(ControlFlow::Continue); - - let mut callback = |event| { - if let ControlFlow::Break = callback(event) { - control_flow.set(ControlFlow::Break); - } - }; - - self.shared.user_callback.store(&mut callback); - - loop { - unsafe { - // First, yield all pending events. - self.shared.call_user_callback_with_pending_events(); - if let ControlFlow::Break = control_flow.get() { - break; - } - - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - - // Wait for the next event. Note that this function blocks during resize. - let ns_event = appkit::NSApp().nextEventMatchingMask_untilDate_inMode_dequeue_( - NSEventMask::NSAnyEventMask.bits() | NSEventMask::NSEventMaskPressure.bits(), - foundation::NSDate::distantFuture(cocoa::base::nil), - foundation::NSDefaultRunLoopMode, - cocoa::base::YES); - - let maybe_event = self.ns_event_to_event(ns_event); - - // Release the pool before calling the top callback in case the user calls either - // `run_forever` or `poll_events` within the callback. - let _: () = msg_send![pool, release]; - - if let Some(event) = maybe_event { - self.shared.user_callback.call_with_event(event); - if let ControlFlow::Break = control_flow.get() { - break; - } - } - } - } - - self.shared.user_callback.drop(); + unimplemented!(); } - // Convert some given `NSEvent` into a winit `Event`. - unsafe fn ns_event_to_event(&mut self, ns_event: cocoa::base::id) -> Option { - if ns_event == cocoa::base::nil { - return None; - } - - // FIXME: Despite not being documented anywhere, an `NSEvent` is produced when a user opens - // Spotlight while the NSApplication is in focus. This `NSEvent` produces a `NSEventType` - // with value `21`. This causes a SEGFAULT as soon as we try to match on the `NSEventType` - // enum as there is no variant associated with the value. Thus, we return early if this - // sneaky event occurs. If someone does find some documentation on this, please fix this by - // adding an appropriate variant to the `NSEventType` enum in the cocoa-rs crate. - if ns_event.eventType() as u64 == 21 { - return None; - } - - let event_type = ns_event.eventType(); - let ns_window = ns_event.window(); - let window_id = super::window::get_window_id(ns_window); - - // FIXME: Document this. Why do we do this? Seems like it passes on events to window/app. - // If we don't do this, window does not become main for some reason. - appkit::NSApp().sendEvent_(ns_event); - - let windows = self.shared.windows.lock().unwrap(); - let maybe_window = windows.iter() - .filter_map(Weak::upgrade) - .find(|window| window_id == window.id()); - - let into_event = |window_event| Event::WindowEvent { - window_id: ::WindowId(window_id), - event: window_event, - }; - - // Returns `Some` window if one of our windows is the key window. - let maybe_key_window = || windows.iter() - .filter_map(Weak::upgrade) - .find(|window| { - let is_key_window: cocoa::base::BOOL = msg_send![*window.window, isKeyWindow]; - is_key_window == cocoa::base::YES - }); - - match event_type { - // https://github.com/glfw/glfw/blob/50eccd298a2bbc272b4977bd162d3e4b55f15394/src/cocoa_window.m#L881 - appkit::NSKeyUp => { - if let Some(key_window) = maybe_key_window() { - if event_mods(ns_event).logo { - let _: () = msg_send![*key_window.window, sendEvent:ns_event]; - } - } - None - }, - // similar to above, but for ``, the keyDown is suppressed instead of the - // KeyUp, and the above trick does not appear to work. - appkit::NSKeyDown => { - let modifiers = event_mods(ns_event); - let keycode = NSEvent::keyCode(ns_event); - if modifiers.logo && keycode == 47 { - modifier_event(ns_event, NSEventModifierFlags::NSCommandKeyMask, false) - .map(into_event) - } else { - None - } - }, - appkit::NSFlagsChanged => { - let mut events = std::collections::VecDeque::new(); - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSShiftKeyMask, - self.modifiers.shift_pressed, - ) { - self.modifiers.shift_pressed = !self.modifiers.shift_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSControlKeyMask, - self.modifiers.ctrl_pressed, - ) { - self.modifiers.ctrl_pressed = !self.modifiers.ctrl_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSCommandKeyMask, - self.modifiers.win_pressed, - ) { - self.modifiers.win_pressed = !self.modifiers.win_pressed; - events.push_back(into_event(window_event)); - } - - if let Some(window_event) = modifier_event( - ns_event, - NSEventModifierFlags::NSAlternateKeyMask, - self.modifiers.alt_pressed, - ) { - self.modifiers.alt_pressed = !self.modifiers.alt_pressed; - events.push_back(into_event(window_event)); - } - - let event = events.pop_front(); - self.shared.pending_events - .lock() - .unwrap() - .extend(events.into_iter()); - event - }, - - appkit::NSMouseEntered => { - let window = match maybe_window.or_else(maybe_key_window) { - Some(window) => window, - None => return None, - }; - - let window_point = ns_event.locationInWindow(); - let view_point = if ns_window == cocoa::base::nil { - let ns_size = foundation::NSSize::new(0.0, 0.0); - let ns_rect = foundation::NSRect::new(window_point, ns_size); - let window_rect = window.window.convertRectFromScreen_(ns_rect); - window.view.convertPoint_fromView_(window_rect.origin, cocoa::base::nil) - } else { - window.view.convertPoint_fromView_(window_point, cocoa::base::nil) - }; - - let view_rect = NSView::frame(*window.view); - let x = view_point.x as f64; - let y = (view_rect.size.height - view_point.y) as f64; - let window_event = WindowEvent::CursorMoved { - device_id: DEVICE_ID, - position: (x, y).into(), - modifiers: event_mods(ns_event), - }; - let event = Event::WindowEvent { window_id: ::WindowId(window.id()), event: window_event }; - self.shared.pending_events.lock().unwrap().push_back(event); - Some(into_event(WindowEvent::CursorEntered { device_id: DEVICE_ID })) - }, - appkit::NSMouseExited => { Some(into_event(WindowEvent::CursorLeft { device_id: DEVICE_ID })) }, - - appkit::NSMouseMoved | - appkit::NSLeftMouseDragged | - appkit::NSOtherMouseDragged | - appkit::NSRightMouseDragged => { - // If the mouse movement was on one of our windows, use it. - // Otherwise, if one of our windows is the key window (receiving input), use it. - // Otherwise, return `None`. - match maybe_window.or_else(maybe_key_window) { - Some(_window) => (), - None => return None, - } - - let mut events = std::collections::VecDeque::with_capacity(3); - - let delta_x = ns_event.deltaX() as f64; - if delta_x != 0.0 { - let motion_event = DeviceEvent::Motion { axis: 0, value: delta_x }; - let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; - events.push_back(event); - } - - let delta_y = ns_event.deltaY() as f64; - if delta_y != 0.0 { - let motion_event = DeviceEvent::Motion { axis: 1, value: delta_y }; - let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; - events.push_back(event); - } - - if delta_x != 0.0 || delta_y != 0.0 { - let motion_event = DeviceEvent::MouseMotion { delta: (delta_x, delta_y) }; - let event = Event::DeviceEvent { device_id: DEVICE_ID, event: motion_event }; - events.push_back(event); - } - - let event = events.pop_front(); - self.shared.pending_events.lock().unwrap().extend(events.into_iter()); - event - }, - - appkit::NSScrollWheel => { - // If none of the windows received the scroll, return `None`. - if maybe_window.is_none() { - return None; - } - - use events::MouseScrollDelta::{LineDelta, PixelDelta}; - let delta = if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - // TODO: This is probably wrong - LineDelta( - ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32, - ) - }; - let phase = match ns_event.phase() { - NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, - NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, - _ => TouchPhase::Moved, - }; - self.shared.pending_events.lock().unwrap().push_back(Event::DeviceEvent { - device_id: DEVICE_ID, - event: DeviceEvent::MouseWheel { - delta: if ns_event.hasPreciseScrollingDeltas() == cocoa::base::YES { - PixelDelta(( - ns_event.scrollingDeltaX() as f64, - ns_event.scrollingDeltaY() as f64, - ).into()) - } else { - LineDelta( - ns_event.scrollingDeltaX() as f32, - ns_event.scrollingDeltaY() as f32, - ) - }, - } - }); - let window_event = WindowEvent::MouseWheel { device_id: DEVICE_ID, delta: delta, phase: phase, modifiers: event_mods(ns_event) }; - Some(into_event(window_event)) - }, + pub fn create_proxy(&self) -> Proxy { + Proxy::new(self.window_target.p.sender.clone()) + } +} - appkit::NSEventTypePressure => { - let pressure = ns_event.pressure(); - let stage = ns_event.stage(); - let window_event = WindowEvent::TouchpadPressure { device_id: DEVICE_ID, pressure: pressure, stage: stage }; - Some(into_event(window_event)) - }, +#[derive(Clone)] +pub struct Proxy { + sender: mpsc::Sender, + source: CFRunLoopSourceRef, +} - appkit::NSApplicationDefined => match ns_event.subtype() { - appkit::NSEventSubtype::NSApplicationActivatedEventType => { - Some(Event::Awakened) - }, - _ => None, - }, +unsafe impl Send for Proxy {} +unsafe impl Sync for Proxy {} - _ => None, +impl Proxy { + fn new(sender: mpsc::Sender) -> Self { + unsafe { + // just wakeup the eventloop + extern "C" fn event_loop_proxy_handler(_: *mut c_void) {} + + // adding a Source to the main CFRunLoop lets us wake it up and + // process user events through the normal OS EventLoop mechanisms. + let rl = CFRunLoopGetMain(); + let mut context: CFRunLoopSourceContext = mem::zeroed(); + context.perform = event_loop_proxy_handler; + let source = CFRunLoopSourceCreate( + ptr::null_mut(), + CFIndex::max_value() - 1, + &mut context, + ); + CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); + CFRunLoopWakeUp(rl); + + Proxy { sender, source } } } - pub fn create_proxy(&self) -> Proxy { - Proxy {} - } - -} - -impl Proxy { - pub fn wakeup(&self) -> Result<(), EventLoopClosed> { - // Awaken the event loop by triggering `NSApplicationActivatedEventType`. + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.sender.send(event).map_err(|_| EventLoopClosed)?; unsafe { - let pool = foundation::NSAutoreleasePool::new(cocoa::base::nil); - let event = - NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2_( - cocoa::base::nil, - appkit::NSApplicationDefined, - foundation::NSPoint::new(0.0, 0.0), - appkit::NSEventModifierFlags::empty(), - 0.0, - 0, - cocoa::base::nil, - appkit::NSEventSubtype::NSApplicationActivatedEventType, - 0, - 0); - appkit::NSApp().postEvent_atStart_(event, cocoa::base::NO); - foundation::NSAutoreleasePool::drain(pool); + // let the main thread know there's a new event + CFRunLoopSourceSignal(self.source); + let rl = CFRunLoopGetMain(); + CFRunLoopWakeUp(rl); } Ok(()) } } - -pub fn char_to_keycode(c: char) -> Option { - // We only translate keys that are affected by keyboard layout. - // - // Note that since keys are translated in a somewhat "dumb" way (reading character) - // there is a concern that some combination, i.e. Cmd+char, causes the wrong - // letter to be received, and so we receive the wrong key. - // - // Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626 - Some(match c { - 'a' | 'A' => events::VirtualKeyCode::A, - 'b' | 'B' => events::VirtualKeyCode::B, - 'c' | 'C' => events::VirtualKeyCode::C, - 'd' | 'D' => events::VirtualKeyCode::D, - 'e' | 'E' => events::VirtualKeyCode::E, - 'f' | 'F' => events::VirtualKeyCode::F, - 'g' | 'G' => events::VirtualKeyCode::G, - 'h' | 'H' => events::VirtualKeyCode::H, - 'i' | 'I' => events::VirtualKeyCode::I, - 'j' | 'J' => events::VirtualKeyCode::J, - 'k' | 'K' => events::VirtualKeyCode::K, - 'l' | 'L' => events::VirtualKeyCode::L, - 'm' | 'M' => events::VirtualKeyCode::M, - 'n' | 'N' => events::VirtualKeyCode::N, - 'o' | 'O' => events::VirtualKeyCode::O, - 'p' | 'P' => events::VirtualKeyCode::P, - 'q' | 'Q' => events::VirtualKeyCode::Q, - 'r' | 'R' => events::VirtualKeyCode::R, - 's' | 'S' => events::VirtualKeyCode::S, - 't' | 'T' => events::VirtualKeyCode::T, - 'u' | 'U' => events::VirtualKeyCode::U, - 'v' | 'V' => events::VirtualKeyCode::V, - 'w' | 'W' => events::VirtualKeyCode::W, - 'x' | 'X' => events::VirtualKeyCode::X, - 'y' | 'Y' => events::VirtualKeyCode::Y, - 'z' | 'Z' => events::VirtualKeyCode::Z, - '1' | '!' => events::VirtualKeyCode::Key1, - '2' | '@' => events::VirtualKeyCode::Key2, - '3' | '#' => events::VirtualKeyCode::Key3, - '4' | '$' => events::VirtualKeyCode::Key4, - '5' | '%' => events::VirtualKeyCode::Key5, - '6' | '^' => events::VirtualKeyCode::Key6, - '7' | '&' => events::VirtualKeyCode::Key7, - '8' | '*' => events::VirtualKeyCode::Key8, - '9' | '(' => events::VirtualKeyCode::Key9, - '0' | ')' => events::VirtualKeyCode::Key0, - '=' | '+' => events::VirtualKeyCode::Equals, - '-' | '_' => events::VirtualKeyCode::Minus, - ']' | '}' => events::VirtualKeyCode::RBracket, - '[' | '{' => events::VirtualKeyCode::LBracket, - '\''| '"' => events::VirtualKeyCode::Apostrophe, - ';' | ':' => events::VirtualKeyCode::Semicolon, - '\\'| '|' => events::VirtualKeyCode::Backslash, - ',' | '<' => events::VirtualKeyCode::Comma, - '/' | '?' => events::VirtualKeyCode::Slash, - '.' | '>' => events::VirtualKeyCode::Period, - '`' | '~' => events::VirtualKeyCode::Grave, - _ => return None, - }) -} - -pub fn scancode_to_keycode(code: c_ushort) -> Option { - Some(match code { - 0x00 => events::VirtualKeyCode::A, - 0x01 => events::VirtualKeyCode::S, - 0x02 => events::VirtualKeyCode::D, - 0x03 => events::VirtualKeyCode::F, - 0x04 => events::VirtualKeyCode::H, - 0x05 => events::VirtualKeyCode::G, - 0x06 => events::VirtualKeyCode::Z, - 0x07 => events::VirtualKeyCode::X, - 0x08 => events::VirtualKeyCode::C, - 0x09 => events::VirtualKeyCode::V, - //0x0a => World 1, - 0x0b => events::VirtualKeyCode::B, - 0x0c => events::VirtualKeyCode::Q, - 0x0d => events::VirtualKeyCode::W, - 0x0e => events::VirtualKeyCode::E, - 0x0f => events::VirtualKeyCode::R, - 0x10 => events::VirtualKeyCode::Y, - 0x11 => events::VirtualKeyCode::T, - 0x12 => events::VirtualKeyCode::Key1, - 0x13 => events::VirtualKeyCode::Key2, - 0x14 => events::VirtualKeyCode::Key3, - 0x15 => events::VirtualKeyCode::Key4, - 0x16 => events::VirtualKeyCode::Key6, - 0x17 => events::VirtualKeyCode::Key5, - 0x18 => events::VirtualKeyCode::Equals, - 0x19 => events::VirtualKeyCode::Key9, - 0x1a => events::VirtualKeyCode::Key7, - 0x1b => events::VirtualKeyCode::Minus, - 0x1c => events::VirtualKeyCode::Key8, - 0x1d => events::VirtualKeyCode::Key0, - 0x1e => events::VirtualKeyCode::RBracket, - 0x1f => events::VirtualKeyCode::O, - 0x20 => events::VirtualKeyCode::U, - 0x21 => events::VirtualKeyCode::LBracket, - 0x22 => events::VirtualKeyCode::I, - 0x23 => events::VirtualKeyCode::P, - 0x24 => events::VirtualKeyCode::Return, - 0x25 => events::VirtualKeyCode::L, - 0x26 => events::VirtualKeyCode::J, - 0x27 => events::VirtualKeyCode::Apostrophe, - 0x28 => events::VirtualKeyCode::K, - 0x29 => events::VirtualKeyCode::Semicolon, - 0x2a => events::VirtualKeyCode::Backslash, - 0x2b => events::VirtualKeyCode::Comma, - 0x2c => events::VirtualKeyCode::Slash, - 0x2d => events::VirtualKeyCode::N, - 0x2e => events::VirtualKeyCode::M, - 0x2f => events::VirtualKeyCode::Period, - 0x30 => events::VirtualKeyCode::Tab, - 0x31 => events::VirtualKeyCode::Space, - 0x32 => events::VirtualKeyCode::Grave, - 0x33 => events::VirtualKeyCode::Back, - //0x34 => unkown, - 0x35 => events::VirtualKeyCode::Escape, - 0x36 => events::VirtualKeyCode::RWin, - 0x37 => events::VirtualKeyCode::LWin, - 0x38 => events::VirtualKeyCode::LShift, - //0x39 => Caps lock, - 0x3a => events::VirtualKeyCode::LAlt, - 0x3b => events::VirtualKeyCode::LControl, - 0x3c => events::VirtualKeyCode::RShift, - 0x3d => events::VirtualKeyCode::RAlt, - 0x3e => events::VirtualKeyCode::RControl, - //0x3f => Fn key, - 0x40 => events::VirtualKeyCode::F17, - 0x41 => events::VirtualKeyCode::Decimal, - //0x42 -> unkown, - 0x43 => events::VirtualKeyCode::Multiply, - //0x44 => unkown, - 0x45 => events::VirtualKeyCode::Add, - //0x46 => unkown, - 0x47 => events::VirtualKeyCode::Numlock, - //0x48 => KeypadClear, - 0x49 => events::VirtualKeyCode::VolumeUp, - 0x4a => events::VirtualKeyCode::VolumeDown, - 0x4b => events::VirtualKeyCode::Divide, - 0x4c => events::VirtualKeyCode::NumpadEnter, - 0x4e => events::VirtualKeyCode::Subtract, - //0x4d => unkown, - 0x4e => events::VirtualKeyCode::Subtract, - 0x4f => events::VirtualKeyCode::F18, - 0x50 => events::VirtualKeyCode::F19, - 0x51 => events::VirtualKeyCode::NumpadEquals, - 0x52 => events::VirtualKeyCode::Numpad0, - 0x53 => events::VirtualKeyCode::Numpad1, - 0x54 => events::VirtualKeyCode::Numpad2, - 0x55 => events::VirtualKeyCode::Numpad3, - 0x56 => events::VirtualKeyCode::Numpad4, - 0x57 => events::VirtualKeyCode::Numpad5, - 0x58 => events::VirtualKeyCode::Numpad6, - 0x59 => events::VirtualKeyCode::Numpad7, - 0x5a => events::VirtualKeyCode::F20, - 0x5b => events::VirtualKeyCode::Numpad8, - 0x5c => events::VirtualKeyCode::Numpad9, - 0x5d => events::VirtualKeyCode::Yen, - //0x5e => JIS Ro, - //0x5f => unkown, - 0x60 => events::VirtualKeyCode::F5, - 0x61 => events::VirtualKeyCode::F6, - 0x62 => events::VirtualKeyCode::F7, - 0x63 => events::VirtualKeyCode::F3, - 0x64 => events::VirtualKeyCode::F8, - 0x65 => events::VirtualKeyCode::F9, - //0x66 => JIS Eisuu (macOS), - 0x67 => events::VirtualKeyCode::F11, - //0x68 => JIS Kana (macOS), - 0x69 => events::VirtualKeyCode::F13, - 0x6a => events::VirtualKeyCode::F16, - 0x6b => events::VirtualKeyCode::F14, - //0x6c => unkown, - 0x6d => events::VirtualKeyCode::F10, - //0x6e => unkown, - 0x6f => events::VirtualKeyCode::F12, - //0x70 => unkown, - 0x71 => events::VirtualKeyCode::F15, - 0x72 => events::VirtualKeyCode::Insert, - 0x73 => events::VirtualKeyCode::Home, - 0x74 => events::VirtualKeyCode::PageUp, - 0x75 => events::VirtualKeyCode::Delete, - 0x76 => events::VirtualKeyCode::F4, - 0x77 => events::VirtualKeyCode::End, - 0x78 => events::VirtualKeyCode::F2, - 0x79 => events::VirtualKeyCode::PageDown, - 0x7a => events::VirtualKeyCode::F1, - 0x7b => events::VirtualKeyCode::Left, - 0x7c => events::VirtualKeyCode::Right, - 0x7d => events::VirtualKeyCode::Down, - 0x7e => events::VirtualKeyCode::Up, - //0x7f => unkown, - - 0xa => events::VirtualKeyCode::Caret, - _ => return None, - }) -} - -pub fn check_function_keys( - s: &String -) -> Option { - if let Some(ch) = s.encode_utf16().next() { - return Some(match ch { - 0xf718 => events::VirtualKeyCode::F21, - 0xf719 => events::VirtualKeyCode::F22, - 0xf71a => events::VirtualKeyCode::F23, - 0xf71b => events::VirtualKeyCode::F24, - _ => return None, - }) - } - - None -} - -pub fn event_mods(event: cocoa::base::id) -> ModifiersState { - let flags = unsafe { - NSEvent::modifierFlags(event) - }; - ModifiersState { - shift: flags.contains(NSEventModifierFlags::NSShiftKeyMask), - ctrl: flags.contains(NSEventModifierFlags::NSControlKeyMask), - alt: flags.contains(NSEventModifierFlags::NSAlternateKeyMask), - logo: flags.contains(NSEventModifierFlags::NSCommandKeyMask), - } -} - -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] - } -} - -unsafe fn modifier_event( - ns_event: cocoa::base::id, - keymask: NSEventModifierFlags, - was_key_pressed: bool, -) -> Option { - if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask) - || was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask) { - let state = if was_key_pressed { - ElementState::Released - } else { - ElementState::Pressed - }; - - let scancode = get_scancode(ns_event); - let virtual_keycode = scancode_to_keycode(scancode); - Some(WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state, - scancode: scancode as u32, - virtual_keycode, - modifiers: event_mods(ns_event), - }, - }) - } else { - None - } -} - -// Constant device ID, to be removed when this backend is updated to report real device IDs. -pub const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId); diff --git a/src/platform_impl/macos/mod.rs b/src/platform_impl/macos/mod.rs index 5a87c54cb78..0e04d369aa1 100644 --- a/src/platform_impl/macos/mod.rs +++ b/src/platform_impl/macos/mod.rs @@ -1,9 +1,30 @@ #![cfg(target_os = "macos")] -pub use self::event_loop::{EventLoop, Proxy as EventLoopProxy}; -pub use self::monitor::MonitorHandle; -pub use self::window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, Window2}; -use std::sync::Arc; +mod app; +mod app_delegate; +mod app_state; +mod event; +mod event_loop; +mod ffi; +mod monitor; +mod observer; +mod util; +mod view; +mod window; +mod window_delegate; + +use std::{ops::Deref, sync::Arc}; + +use { + event::DeviceId as RootDeviceId, window::{CreationError, WindowAttributes}, +}; +pub use self::{ + event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy}, + monitor::MonitorHandle, + window::{ + Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow, + }, +}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DeviceId; @@ -14,38 +35,33 @@ impl DeviceId { } } -use {CreationError}; +// 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 { - pub window: Arc, + window: Arc, + // We keep this around so that it doesn't get dropped until the window does. + _delegate: util::IdRef, } -impl ::std::ops::Deref for Window { - type Target = Window2; +unsafe impl Send for Window {} +unsafe impl Sync for Window {} + +impl Deref for Window { + type Target = UnownedWindow; #[inline] - fn deref(&self) -> &Window2 { + fn deref(&self) -> &Self::Target { &*self.window } } impl Window { - - pub fn new(event_loop: &EventLoop, - attributes: ::WindowAttributes, - pl_attribs: PlatformSpecificWindowBuilderAttributes) -> Result - { - let weak_shared = Arc::downgrade(&event_loop.shared); - let window = Arc::new(try!(Window2::new(weak_shared, attributes, pl_attribs))); - let weak_window = Arc::downgrade(&window); - event_loop.shared.windows.lock().unwrap().push(weak_window); - Ok(Window { window: window }) + pub fn new( + _window_target: &EventLoopWindowTarget, + attributes: WindowAttributes, + pl_attribs: PlatformSpecificWindowBuilderAttributes, + ) -> Result { + let (window, _delegate) = UnownedWindow::new(attributes, pl_attribs)?; + Ok(Window { window, _delegate }) } - } - -mod event_loop; -mod ffi; -mod monitor; -mod util; -mod view; -mod window; diff --git a/src/platform_impl/macos/monitor.rs b/src/platform_impl/macos/monitor.rs index b9fb053672f..c3c6ce2dfd4 100644 --- a/src/platform_impl/macos/monitor.rs +++ b/src/platform_impl/macos/monitor.rs @@ -3,9 +3,8 @@ use std::{collections::VecDeque, fmt}; use cocoa::{appkit::NSScreen, base::{id, nil}, foundation::{NSString, NSUInteger}}; use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds}; -use crate::dpi::{PhysicalPosition, PhysicalSize}; -use super::EventLoop; -use super::window::{IdRef, Window2}; +use dpi::{PhysicalPosition, PhysicalSize}; +use platform_impl::platform::util::IdRef; #[derive(Clone, PartialEq)] pub struct MonitorHandle(CGDirectDisplayID); @@ -23,37 +22,7 @@ pub fn get_available_monitors() -> VecDeque { } pub fn get_primary_monitor() -> MonitorHandle { - let id = MonitorHandle(CGDisplay::main().id); - id -} - -impl EventLoop { - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() - } - - pub fn make_monitor_from_display(id: CGDirectDisplayID) -> MonitorHandle { - let id = MonitorHandle(id); - id - } -} - -impl Window2 { - #[inline] - pub fn get_available_monitors(&self) -> VecDeque { - get_available_monitors() - } - - #[inline] - pub fn get_primary_monitor(&self) -> MonitorHandle { - get_primary_monitor() - } + MonitorHandle(CGDisplay::main().id) } impl fmt::Debug for MonitorHandle { diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index e741188af04..dd017555a38 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -1,10 +1,9 @@ use cocoa::{ - appkit::NSImage, base::{id, nil, YES}, + appkit::NSImage, base::{id, nil}, foundation::{NSDictionary, NSPoint, NSString}, }; use objc::runtime::Sel; -use super::IntoOption; use window::MouseCursor; pub enum Cursor { @@ -89,21 +88,17 @@ impl Cursor { }; msg_send![class, performSelector:sel] }, - Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name) - .unwrap_or_else(|message| { - warn!("{}", message); - Self::default().load() - }), + Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name), } } } // Note that loading `busybutclickable` with this code won't animate the frames; // instead you'll just get them all in a column. -unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result { +pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { static CURSOR_ROOT: &'static str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors"; let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT); - let cursor_name = NSString::alloc(nil).init_str(cursor_name_str); + let cursor_name = NSString::alloc(nil).init_str(cursor_name); let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf"); let cursor_plist = NSString::alloc(nil).init_str("info.plist"); let key_x = NSString::alloc(nil).init_str("hotx"); @@ -119,20 +114,11 @@ unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result { stringByAppendingPathComponent:cursor_plist ]; - let image = NSImage::alloc(nil) - .initByReferencingFile_(pdf_path) - // This will probably never be `None`, since images are loaded lazily... - .into_option() - // because of that, we need to check for validity. - .filter(|image| image.isValid() == YES) - .ok_or_else(|| - format!("Failed to read image for `{}` cursor", cursor_name_str) - )?; - let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path) - .into_option() - .ok_or_else(|| - format!("Failed to read info for `{}` cursor", cursor_name_str) - )?; + let image = NSImage::alloc(nil).initByReferencingFile_(pdf_path); + let info = NSDictionary::dictionaryWithContentsOfFile_( + nil, + info_path, + ); let x = info.valueForKey_(key_x); let y = info.valueForKey_(key_y); let point = NSPoint::new( @@ -140,10 +126,8 @@ unsafe fn load_webkit_cursor(cursor_name_str: &str) -> Result { msg_send![y, doubleValue], ); let cursor: id = msg_send![class!(NSCursor), alloc]; - let cursor: id = msg_send![cursor, initWithImage:image hotSpot:point]; - cursor - .into_option() - .ok_or_else(|| - format!("Failed to initialize `{}` cursor", cursor_name_str) - ) + msg_send![cursor, + initWithImage:image + hotSpot:point + ] } diff --git a/src/platform_impl/macos/util/mod.rs b/src/platform_impl/macos/util/mod.rs index 627f667430e..a8d6a83b44f 100644 --- a/src/platform_impl/macos/util/mod.rs +++ b/src/platform_impl/macos/util/mod.rs @@ -1,48 +1,90 @@ +mod async; mod cursor; -mod into_option; -pub use self::{cursor::Cursor, into_option::IntoOption}; +pub use self::{async::*, cursor::*}; -use cocoa::appkit::NSWindowStyleMask; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSRect, NSUInteger}; +use std::ops::Deref; +use std::ops::BitAnd; + +use cocoa::{ + appkit::{NSApp, NSWindowStyleMask}, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSRect, NSUInteger}, +}; use core_graphics::display::CGDisplay; -use objc::runtime::{Class, Object}; +use objc::runtime::{BOOL, Class, Object, Sel, YES}; use platform_impl::platform::ffi; -use platform_impl::platform::window::IdRef; + +// Replace with `!` once stable +#[derive(Debug)] +pub enum Never {} + +pub fn has_flag(bitset: T, flag: T) -> bool +where T: + Copy + PartialEq + BitAnd +{ + bitset & flag == flag +} pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange { location: ffi::NSNotFound as NSUInteger, length: 0, }; -// For consistency with other platforms, this will... -// 1. translate the bottom-left window corner into the top-left window corner -// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one -pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { - CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) +pub struct IdRef(id); + +impl IdRef { + pub fn new(inner: id) -> IdRef { + IdRef(inner) + } + + #[allow(dead_code)] + pub fn retain(inner: id) -> IdRef { + if inner != nil { + let () = unsafe { msg_send![inner, retain] }; + } + IdRef(inner) + } + + pub fn non_nil(self) -> Option { + if self.0 == nil { None } else { Some(self) } + } } -pub unsafe fn set_style_mask(window: id, view: id, mask: NSWindowStyleMask) { - use cocoa::appkit::NSWindow; - window.setStyleMask_(mask); - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! - window.makeFirstResponder_(view); +impl Drop for IdRef { + fn drop(&mut self) { + if self.0 != nil { + unsafe { + let pool = NSAutoreleasePool::new(nil); + let () = msg_send![self.0, release]; + pool.drain(); + }; + } + } } -pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) { - use cocoa::appkit::NSWindow; +impl Deref for IdRef { + type Target = id; + fn deref<'a>(&'a self) -> &'a id { + &self.0 + } +} - let current_style_mask = window.styleMask(); - if on { - window.setStyleMask_(current_style_mask | mask); - } else { - window.setStyleMask_(current_style_mask & (!mask)); +impl Clone for IdRef { + fn clone(&self) -> IdRef { + if self.0 != nil { + let _: id = unsafe { msg_send![self.0, retain] }; + } + IdRef(self.0) } +} - // If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly! - window.makeFirstResponder_(view); +// For consistency with other platforms, this will... +// 1. translate the bottom-left window corner into the top-left window corner +// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one +pub fn bottom_left_to_top_left(rect: NSRect) -> f64 { + CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) } pub unsafe fn superclass<'a>(this: &'a Object) -> &'a Class { @@ -58,6 +100,24 @@ pub unsafe fn create_input_context(view: id) -> IdRef { #[allow(dead_code)] pub unsafe fn open_emoji_picker() { - let app: id = msg_send![class!(NSApplication), sharedApplication]; - let _: () = msg_send![app, orderFrontCharacterPalette:nil]; + let () = msg_send![NSApp(), orderFrontCharacterPalette:nil]; } + +pub extern fn yes(_: &Object, _: Sel) -> BOOL { + YES +} + +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); +} + diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index d6e7333110c..cceba6eeeeb 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -1,66 +1,74 @@ -// This is a pretty close port of the implementation in GLFW: -// https://github.com/glfw/glfw/blob/7ef34eb06de54dd9186d3d21a401b2ef819b59e7/src/cocoa_window.m - -use std::{slice, str}; -use std::boxed::Box; -use std::collections::VecDeque; -use std::os::raw::*; -use std::sync::{Arc, Mutex, Weak}; - -use cocoa::base::{id, nil}; -use cocoa::appkit::{NSEvent, NSView, NSWindow}; -use cocoa::foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}; -use objc::declare::ClassDecl; -use objc::runtime::{Class, Object, Protocol, Sel, BOOL, YES}; - -use {ElementState, Event, KeyboardInput, MouseButton, WindowEvent, WindowId}; -use platform_impl::platform::event_loop::{DEVICE_ID, event_mods, Shared, to_virtual_key_code, check_additional_virtual_key_codes, get_scancode}; -use platform_impl::platform::util; -use platform_impl::platform::ffi::*; -use platform_impl::platform::window::{get_window_id, IdRef}; -use event; +use std::{ + boxed::Box, collections::VecDeque, os::raw::*, slice, str, + sync::{Arc, Mutex, Weak}, +}; + +use cocoa::{ + appkit::{NSApp, NSEvent, NSEventModifierFlags, NSEventPhase, NSView, NSWindow}, + base::{id, nil}, foundation::{NSPoint, NSRect, NSSize, NSString, NSUInteger}, +}; +use objc::{declare::ClassDecl, runtime::{BOOL, Class, NO, Object, Protocol, Sel, YES}}; + +use { + event::{ + DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, + MouseScrollDelta, TouchPhase, VirtualKeyCode, WindowEvent, + }, + window::WindowId, +}; +use platform_impl::platform::{ + app_state::AppState, DEVICE_ID, + event::{check_function_keys, event_mods, modifier_event, to_virtual_keycode}, + util::{self, IdRef}, ffi::*, window::get_window_id, +}; + +#[derive(Default)] +struct Modifiers { + shift_pressed: bool, + ctrl_pressed: bool, + win_pressed: bool, + alt_pressed: bool, +} struct ViewState { - window: id, - shared: Weak, - cursor: Arc>, + nswindow: id, + pub cursor: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, + modifiers: Modifiers, } -pub fn new_view(window: id, shared: Weak) -> (IdRef, Weak>) { +pub fn new_view(nswindow: id) -> (IdRef, Weak>) { let cursor = Default::default(); let cursor_access = Arc::downgrade(&cursor); let state = ViewState { - window, - shared, + nswindow, cursor, ime_spot: None, raw_characters: None, is_key_down: false, + modifiers: Default::default(), }; unsafe { // This is free'd in `dealloc` let state_ptr = Box::into_raw(Box::new(state)) as *mut c_void; - let view: id = msg_send![VIEW_CLASS.0, alloc]; - (IdRef::new(msg_send![view, initWithWinit:state_ptr]), cursor_access) + let nsview: id = msg_send![VIEW_CLASS.0, alloc]; + (IdRef::new(msg_send![nsview, initWithWinit:state_ptr]), cursor_access) } } -pub fn set_ime_spot(view: id, input_context: id, x: f64, y: f64) { - unsafe { - let state_ptr: *mut c_void = *(*view).get_mut_ivar("winitState"); - let state = &mut *(state_ptr as *mut ViewState); - let content_rect = NSWindow::contentRectForFrameRect_( - state.window, - NSWindow::frame(state.window), - ); - let base_x = content_rect.origin.x as f64; - let base_y = (content_rect.origin.y + content_rect.size.height) as f64; - state.ime_spot = Some((base_x + x, base_y - y)); - let _: () = msg_send![input_context, invalidateCharacterCoordinates]; - } +pub unsafe fn set_ime_spot(nsview: id, input_context: id, x: f64, y: f64) { + let state_ptr: *mut c_void = *(*nsview).get_mut_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + let content_rect = NSWindow::contentRectForFrameRect_( + state.nswindow, + NSWindow::frame(state.nswindow), + ); + let base_x = content_rect.origin.x as f64; + let base_y = (content_rect.origin.y + content_rect.size.height) as f64; + state.ime_spot = Some((base_x + x, base_y - y)); + let _: () = msg_send![input_context, invalidateCharacterCoordinates]; } struct ViewClass(*const Class); @@ -71,38 +79,61 @@ lazy_static! { static ref VIEW_CLASS: ViewClass = unsafe { let superclass = class!(NSView); let mut decl = ClassDecl::new("WinitView", superclass).unwrap(); - decl.add_method(sel!(dealloc), dealloc as extern fn(&Object, Sel)); + decl.add_method( + sel!(dealloc), + dealloc as extern fn(&Object, Sel), + ); decl.add_method( sel!(initWithWinit:), init_with_winit as extern fn(&Object, Sel, *mut c_void) -> id, ); + decl.add_method( + sel!(viewDidMoveToWindow), + view_did_move_to_window as extern fn(&Object, Sel), + ); decl.add_method( sel!(drawRect:), - draw_rect as extern fn(&Object, Sel, NSRect), + draw_rect as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(acceptsFirstResponder), + accepts_first_responder as extern fn(&Object, Sel) -> BOOL, + ); + decl.add_method( + sel!(touchBar), + touch_bar as extern fn(&Object, Sel) -> BOOL, ); decl.add_method( sel!(resetCursorRects), reset_cursor_rects as extern fn(&Object, Sel), ); - decl.add_method(sel!(hasMarkedText), has_marked_text as extern fn(&Object, Sel) -> BOOL); + decl.add_method( + sel!(hasMarkedText), + has_marked_text as extern fn(&Object, Sel) -> BOOL, + ); decl.add_method( sel!(markedRange), marked_range as extern fn(&Object, Sel) -> NSRange, ); - decl.add_method(sel!(selectedRange), selected_range as extern fn(&Object, Sel) -> NSRange); + decl.add_method( + sel!(selectedRange), + selected_range as extern fn(&Object, Sel) -> NSRange, + ); decl.add_method( sel!(setMarkedText:selectedRange:replacementRange:), set_marked_text as extern fn(&mut Object, Sel, id, NSRange, NSRange), ); - decl.add_method(sel!(unmarkText), unmark_text as extern fn(&Object, Sel)); + decl.add_method( + sel!(unmarkText), + unmark_text as extern fn(&Object, Sel), + ); decl.add_method( sel!(validAttributesForMarkedText), valid_attributes_for_marked_text as extern fn(&Object, Sel) -> id, ); decl.add_method( sel!(attributedSubstringForProposedRange:actualRange:), - attributed_substring_for_proposed_range - as extern fn(&Object, Sel, NSRange, *mut c_void) -> id, + attributed_substring_for_proposed_range as extern fn(&Object, Sel, NSRange, *mut c_void) -> id, ); decl.add_method( sel!(insertText:replacementRange:), @@ -114,28 +145,96 @@ lazy_static! { ); decl.add_method( sel!(firstRectForCharacterRange:actualRange:), - first_rect_for_character_range - as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect, + first_rect_for_character_range as extern fn(&Object, Sel, NSRange, *mut c_void) -> NSRect, ); decl.add_method( sel!(doCommandBySelector:), do_command_by_selector as extern fn(&Object, Sel, Sel), ); - decl.add_method(sel!(keyDown:), key_down as extern fn(&Object, Sel, id)); - decl.add_method(sel!(keyUp:), key_up as extern fn(&Object, Sel, id)); - decl.add_method(sel!(insertTab:), insert_tab as extern fn(&Object, Sel, id)); - decl.add_method(sel!(insertBackTab:), insert_back_tab as extern fn(&Object, Sel, id)); - decl.add_method(sel!(mouseDown:), mouse_down as extern fn(&Object, Sel, id)); - decl.add_method(sel!(mouseUp:), mouse_up as extern fn(&Object, Sel, id)); - decl.add_method(sel!(rightMouseDown:), right_mouse_down as extern fn(&Object, Sel, id)); - decl.add_method(sel!(rightMouseUp:), right_mouse_up as extern fn(&Object, Sel, id)); - decl.add_method(sel!(otherMouseDown:), other_mouse_down as extern fn(&Object, Sel, id)); - decl.add_method(sel!(otherMouseUp:), other_mouse_up as extern fn(&Object, Sel, id)); - decl.add_method(sel!(mouseMoved:), mouse_moved as extern fn(&Object, Sel, id)); - decl.add_method(sel!(mouseDragged:), mouse_dragged as extern fn(&Object, Sel, id)); - decl.add_method(sel!(rightMouseDragged:), right_mouse_dragged as extern fn(&Object, Sel, id)); - decl.add_method(sel!(otherMouseDragged:), other_mouse_dragged as extern fn(&Object, Sel, id)); - decl.add_method(sel!(_wantsKeyDownForEvent:), wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL); + decl.add_method( + sel!(keyDown:), + key_down as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(keyUp:), + key_up as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(flagsChanged:), + flags_changed as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(insertTab:), + insert_tab as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(insertBackTab:), + insert_back_tab as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseDown:), + mouse_down as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseUp:), + mouse_up as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseDown:), + right_mouse_down as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseUp:), + right_mouse_up as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseDown:), + other_mouse_down as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseUp:), + other_mouse_up as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseMoved:), + mouse_moved as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseDragged:), + mouse_dragged as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(rightMouseDragged:), + right_mouse_dragged as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(otherMouseDragged:), + other_mouse_dragged as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseEntered:), + mouse_entered as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(mouseExited:), + mouse_exited as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(scrollWheel:), + scroll_wheel as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(pressureChangeWithEvent:), + pressure_change_with_event as extern fn(&Object, Sel, id), + ); + decl.add_method( + sel!(_wantsKeyDownForEvent:), + wants_key_down_for_event as extern fn(&Object, Sel, id) -> BOOL, + ); + decl.add_method( + sel!(cancelOperation:), + cancel_operation as extern fn(&Object, Sel, id), + ); decl.add_ivar::<*mut c_void>("winitState"); decl.add_ivar::("markedText"); let protocol = Protocol::get("NSTextInputClient").unwrap(); @@ -167,27 +266,43 @@ extern fn init_with_winit(this: &Object, _sel: Sel, state: *mut c_void) -> id { } } -extern fn draw_rect(this: &Object, _sel: Sel, rect: NSRect) { +extern fn view_did_move_to_window(this: &Object, _sel: Sel) { + trace!("Triggered `viewDidMoveToWindow`"); + unsafe { + let rect: NSRect = msg_send![this, visibleRect]; + let _: () = msg_send![this, + addTrackingRect:rect + owner:this + userData:nil + assumeInside:NO + ]; + } + trace!("Completed `viewDidMoveToWindow`"); +} + +extern fn draw_rect(this: &Object, _sel: Sel, rect: id) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); - if let Some(shared) = state.shared.upgrade() { - let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), - event: WindowEvent::Refresh, - }; - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); - } + AppState::queue_redraw(WindowId(get_window_id(state.nswindow))); let superclass = util::superclass(this); let () = msg_send![super(this, superclass), drawRect:rect]; } } +extern fn accepts_first_responder(_this: &Object, _sel: Sel) -> BOOL { + YES +} + +// This is necessary to prevent a beefy terminal error on MacBook Pros: +// IMKInputSession [0x7fc573576ff0 presentFunctionRowItemTextInputViewWithEndpoint:completionHandler:] : [self textInputContext]=0x7fc573558e10 *NO* NSRemoteViewController to client, NSError=Error Domain=NSCocoaErrorDomain Code=4099 "The connection from pid 0 was invalidated from this process." UserInfo={NSDebugDescription=The connection from pid 0 was invalidated from this process.}, com.apple.inputmethod.EmojiFunctionRowItem +// TODO: Add an API extension for using `NSTouchBar` +extern fn touch_bar(_this: &Object, _sel: Sel) -> BOOL { + NO +} + extern fn reset_cursor_rects(this: &Object, _sel: Sel) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); @@ -202,19 +317,22 @@ extern fn reset_cursor_rects(this: &Object, _sel: Sel) { } } + extern fn has_marked_text(this: &Object, _sel: Sel) -> BOOL { - //println!("hasMarkedText"); unsafe { + trace!("Triggered `hasMarkedText`"); let marked_text: id = *this.get_ivar("markedText"); + trace!("Completed `hasMarkedText`"); (marked_text.length() > 0) as i8 } } extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { - //println!("markedRange"); unsafe { + trace!("Triggered `markedRange`"); let marked_text: id = *this.get_ivar("markedText"); let length = marked_text.length(); + trace!("Completed `markedRange`"); if length > 0 { NSRange::new(0, length - 1) } else { @@ -224,7 +342,8 @@ extern fn marked_range(this: &Object, _sel: Sel) -> NSRange { } extern fn selected_range(_this: &Object, _sel: Sel) -> NSRange { - //println!("selectedRange"); + trace!("Triggered `selectedRange`"); + trace!("Completed `selectedRange`"); util::EMPTY_RANGE } @@ -235,7 +354,7 @@ extern fn set_marked_text( _selected_range: NSRange, _replacement_range: NSRange, ) { - //println!("setMarkedText"); + trace!("Triggered `setMarkedText`"); unsafe { let marked_text_ref: &mut id = this.get_mut_ivar("markedText"); let _: () = msg_send![(*marked_text_ref), release]; @@ -248,10 +367,11 @@ extern fn set_marked_text( }; *marked_text_ref = marked_text; } + trace!("Completed `setMarkedText`"); } extern fn unmark_text(this: &Object, _sel: Sel) { - //println!("unmarkText"); + trace!("Triggered `unmarkText`"); unsafe { let marked_text: id = *this.get_ivar("markedText"); let mutable_string = marked_text.mutableString(); @@ -259,10 +379,12 @@ extern fn unmark_text(this: &Object, _sel: Sel) { let input_context: id = msg_send![this, inputContext]; let _: () = msg_send![input_context, discardMarkedText]; } + trace!("Completed `unmarkText`"); } extern fn valid_attributes_for_marked_text(_this: &Object, _sel: Sel) -> id { - //println!("validAttributesForMarkedText"); + trace!("Triggered `validAttributesForMarkedText`"); + trace!("Completed `validAttributesForMarkedText`"); unsafe { msg_send![class!(NSArray), array] } } @@ -272,12 +394,14 @@ extern fn attributed_substring_for_proposed_range( _range: NSRange, _actual_range: *mut c_void, // *mut NSRange ) -> id { - //println!("attributedSubstringForProposedRange"); + trace!("Triggered `attributedSubstringForProposedRange`"); + trace!("Completed `attributedSubstringForProposedRange`"); nil } extern fn character_index_for_point(_this: &Object, _sel: Sel, _point: NSPoint) -> NSUInteger { - //println!("characterIndexForPoint"); + trace!("Triggered `characterIndexForPoint`"); + trace!("Completed `characterIndexForPoint`"); 0 } @@ -287,20 +411,20 @@ extern fn first_rect_for_character_range( _range: NSRange, _actual_range: *mut c_void, // *mut NSRange ) -> NSRect { - //println!("firstRectForCharacterRange"); unsafe { + trace!("Triggered `firstRectForCharacterRange`"); let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let (x, y) = state.ime_spot.unwrap_or_else(|| { let content_rect = NSWindow::contentRectForFrameRect_( - state.window, - NSWindow::frame(state.window), + state.nswindow, + NSWindow::frame(state.nswindow), ); let x = content_rect.origin.x; let y = util::bottom_left_to_top_left(content_rect); (x, y) }); - + trace!("Completed `firstRectForCharacterRange`"); NSRect::new( NSPoint::new(x as _, y as _), NSSize::new(0.0, 0.0), @@ -309,7 +433,7 @@ extern fn first_rect_for_character_range( } extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: NSRange) { - //println!("insertText"); + trace!("Triggered `insertText`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); @@ -331,46 +455,36 @@ extern fn insert_text(this: &Object, _sel: Sel, string: id, _replacement_range: state.is_key_down = true; // We don't need this now, but it's here if that changes. - //let event: id = msg_send![class!(NSApp), currentEvent]; + //let event: id = msg_send![NSApp(), currentEvent]; let mut events = VecDeque::with_capacity(characters.len()); for character in string.chars() { events.push_back(Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter(character), }); } - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .append(&mut events); - } + AppState::queue_events(events); } + trace!("Completed `insertText`"); } extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { - //println!("doCommandBySelector"); + trace!("Triggered `doCommandBySelector`"); // Basically, we're sent this message whenever a keyboard event that doesn't generate a "human readable" character // happens, i.e. newlines, tabs, and Ctrl+C. unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); - let shared = if let Some(shared) = state.shared.upgrade() { - shared - } else { - return; - }; - let mut events = VecDeque::with_capacity(1); if command == sel!(insertNewline:) { // The `else` condition would emit the same character, but I'm keeping this here both... // 1) as a reminder for how `doCommandBySelector` works // 2) to make our use of carriage return explicit events.push_back(Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter('\r'), }); } else { @@ -378,82 +492,47 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { if let Some(raw_characters) = raw_characters { for character in raw_characters.chars() { events.push_back(Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::ReceivedCharacter(character), }); } } }; - shared.pending_events - .lock() - .unwrap() - .append(&mut events); + AppState::queue_events(events); } + trace!("Completed `doCommandBySelector`"); } -fn get_characters(event: id, ignore_modifiers: bool) -> String { +fn get_characters(event: id) -> Option { unsafe { - let characters: id = if ignore_modifiers { - msg_send![event, charactersIgnoringModifiers] - } else { - msg_send![event, characters] - }; - - assert_ne!(characters, nil); + let characters: id = msg_send![event, characters]; let slice = slice::from_raw_parts( characters.UTF8String() as *const c_uchar, characters.len(), ); - let string = str::from_utf8_unchecked(slice); - - string.to_owned() + Some(string.to_owned()) } } -// Retrieves a layout-independent keycode given an event. -fn retrieve_keycode(event: id) -> Option { - #[inline] - fn get_code(ev: id, raw: bool) -> Option { - let characters = get_characters(ev, raw); - characters.chars().next().map_or(None, |c| char_to_keycode(c)) - } - - // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. - // If we don't get a match, then we fall back to unmodified characters. - let code = get_code(event, false) - .or_else(|| { - get_code(event, true) - }); - - // We've checked all layout related keys, so fall through to scancode. - // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). - // - // We're additionally checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - code.or_else(|| { - let scancode = get_scancode(event); - scancode_to_keycode(scancode) - .or_else(|| { - check_function_keys(&get_characters(event, true)) - }) - }) -} - extern fn key_down(this: &Object, _sel: Sel, event: id) { - //println!("keyDown"); + trace!("Triggered `keyDown`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); - let window_id = WindowId(get_window_id(state.window)); - let characters = get_characters(event, false); + let window_id = WindowId(get_window_id(state.nswindow)); - state.raw_characters = Some(characters.clone()); + state.raw_characters = get_characters(event); - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); + let keycode: c_ushort = msg_send![event, keyCode]; + // We are checking here for F21-F24 keys, since their keycode + // can vary, but we know that they are encoded + // in characters property. + let virtual_keycode = to_virtual_keycode(keycode).or_else(|| { + check_function_keys(&state.raw_characters) + }); + let scancode = keycode as u32; let is_repeat = msg_send![event, isARepeat]; let window_event = Event::WindowEvent { @@ -469,47 +548,64 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + let characters: id = msg_send![event, characters]; + let slice = slice::from_raw_parts( + characters.UTF8String() as *const c_uchar, + characters.len(), + ); + let string = str::from_utf8_unchecked(slice); + + state.raw_characters = { + Some(string.to_owned()) + }; + + let pass_along = { + AppState::queue_event(window_event); // Emit `ReceivedCharacter` for key repeats - if is_repeat && state.is_key_down{ - for character in characters.chars() { - let window_event = Event::WindowEvent { + if is_repeat && state.is_key_down { + for character in string.chars() { + AppState::queue_event(Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), - }; - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + }); } + false } else { - // Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do... - // So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some - // keys to generate twice as many characters. - let array: id = msg_send![class!(NSArray), arrayWithObject:event]; - let (): _ = msg_send![this, interpretKeyEvents:array]; + true } + }; + + if pass_along { + // Some keys (and only *some*, with no known reason) don't trigger `insertText`, while others do... + // So, we don't give repeats the opportunity to trigger that, since otherwise our hack will cause some + // keys to generate twice as many characters. + let array: id = msg_send![class!(NSArray), arrayWithObject:event]; + let _: () = msg_send![this, interpretKeyEvents:array]; } } + trace!("Completed `keyDown`"); } extern fn key_up(this: &Object, _sel: Sel, event: id) { - //println!("keyUp"); + trace!("Triggered `keyUp`"); unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); state.is_key_down = false; - let scancode = get_scancode(event) as u32; - let virtual_keycode = retrieve_keycode(event); + // We need characters here to check for additional keys such as + // F21-F24. + let characters = get_characters(event); + let keycode: c_ushort = msg_send![event, keyCode]; + let virtual_keycode = to_virtual_keycode(keycode) + .or_else(|| { + check_function_keys(&characters) + }); + let scancode = keycode as u32; let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { @@ -521,13 +617,63 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); + AppState::queue_event(window_event); + } + trace!("Completed `keyUp`"); +} + +extern fn flags_changed(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `flagsChanged`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let mut events = VecDeque::with_capacity(4); + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSShiftKeyMask, + state.modifiers.shift_pressed, + ) { + state.modifiers.shift_pressed = !state.modifiers.shift_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSControlKeyMask, + state.modifiers.ctrl_pressed, + ) { + state.modifiers.ctrl_pressed = !state.modifiers.ctrl_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSCommandKeyMask, + state.modifiers.win_pressed, + ) { + state.modifiers.win_pressed = !state.modifiers.win_pressed; + events.push_back(window_event); + } + + if let Some(window_event) = modifier_event( + event, + NSEventModifierFlags::NSAlternateKeyMask, + state.modifiers.alt_pressed, + ) { + state.modifiers.alt_pressed = !state.modifiers.alt_pressed; + events.push_back(window_event); + } + + for event in events { + AppState::queue_event(Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event, + }); } } + trace!("Completed `flagsChanged`"); } extern fn insert_tab(this: &Object, _sel: Sel, _sender: id) { @@ -552,13 +698,45 @@ extern fn insert_back_tab(this: &Object, _sel: Sel, _sender: id) { } } +// Allows us to receive Cmd-. (the shortcut for closing a dialog) +// https://bugs.eclipse.org/bugs/show_bug.cgi?id=300620#c6 +extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { + trace!("Triggered `cancelOperation`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let scancode = 0x2f; + let virtual_keycode = to_virtual_keycode(scancode); + debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); + + let event: id = msg_send![NSApp(), currentEvent]; + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::KeyboardInput { + device_id: DEVICE_ID, + input: KeyboardInput { + state: ElementState::Pressed, + scancode: scancode as _, + virtual_keycode, + modifiers: event_mods(event), + }, + }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `cancelOperation`"); +} + fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: ElementState) { unsafe { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::MouseInput { device_id: DEVICE_ID, state: button_state, @@ -567,12 +745,7 @@ fn mouse_click(this: &Object, event: id, button: MouseButton, button_state: Elem }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); - } + AppState::queue_event(window_event); } } @@ -624,7 +797,7 @@ fn mouse_motion(this: &Object, event: id) { let y = view_rect.size.height as f64 - view_point.y as f64; let window_event = Event::WindowEvent { - window_id: WindowId(get_window_id(state.window)), + window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::CursorMoved { device_id: DEVICE_ID, position: (x, y).into(), @@ -632,12 +805,7 @@ fn mouse_motion(this: &Object, event: id) { }, }; - if let Some(shared) = state.shared.upgrade() { - shared.pending_events - .lock() - .unwrap() - .push_back(window_event); - } + AppState::queue_event(window_event); } } @@ -657,7 +825,125 @@ extern fn other_mouse_dragged(this: &Object, _sel: Sel, event: id) { mouse_motion(this, event); } +extern fn mouse_entered(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `mouseEntered`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let enter_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorEntered { device_id: DEVICE_ID }, + }; + + let move_event = { + let window_point = event.locationInWindow(); + let view_point: NSPoint = msg_send![this, + convertPoint:window_point + fromView:nil // convert from window coordinates + ]; + let view_rect: NSRect = msg_send![this, frame]; + let x = view_point.x as f64; + let y = (view_rect.size.height - view_point.y) as f64; + Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorMoved { + device_id: DEVICE_ID, + position: (x, y).into(), + modifiers: event_mods(event), + } + } + }; + + AppState::queue_event(enter_event); + AppState::queue_event(move_event); + } + trace!("Completed `mouseEntered`"); +} + +extern fn mouse_exited(this: &Object, _sel: Sel, _event: id) { + trace!("Triggered `mouseExited`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::CursorLeft { device_id: DEVICE_ID }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `mouseExited`"); +} + +extern fn scroll_wheel(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `scrollWheel`"); + unsafe { + let delta = { + let (x, y) = (event.scrollingDeltaX(), event.scrollingDeltaY()); + if event.hasPreciseScrollingDeltas() == YES { + MouseScrollDelta::PixelDelta((x as f64, y as f64).into()) + } else { + MouseScrollDelta::LineDelta(x as f32, y as f32) + } + }; + let phase = match event.phase() { + NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => TouchPhase::Started, + NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended, + _ => TouchPhase::Moved, + }; + + let device_event = Event::DeviceEvent { + device_id: DEVICE_ID, + event: DeviceEvent::MouseWheel { delta }, + }; + + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::MouseWheel { + device_id: DEVICE_ID, + delta, + phase, + modifiers: event_mods(event), + }, + }; + + AppState::queue_event(device_event); + AppState::queue_event(window_event); + } + trace!("Completed `scrollWheel`"); +} + +extern fn pressure_change_with_event(this: &Object, _sel: Sel, event: id) { + trace!("Triggered `pressureChangeWithEvent`"); + unsafe { + let state_ptr: *mut c_void = *this.get_ivar("winitState"); + let state = &mut *(state_ptr as *mut ViewState); + + let pressure = event.pressure(); + let stage = event.stage(); + + let window_event = Event::WindowEvent { + window_id: WindowId(get_window_id(state.nswindow)), + event: WindowEvent::TouchpadPressure { + device_id: DEVICE_ID, + pressure, + stage, + }, + }; + + AppState::queue_event(window_event); + } + trace!("Completed `pressureChangeWithEvent`"); +} + +// Allows us to receive Ctrl-Tab and Ctrl-Esc. +// Note that this *doesn't* help with any missing Cmd inputs. // https://github.com/chromium/chromium/blob/a86a8a6bcfa438fa3ac2eba6f02b3ad1f8e0756f/ui/views/cocoa/bridged_content_view.mm#L816 -extern fn wants_key_down_for_event(_this: &Object, _se: Sel, _event: id) -> BOOL { +extern fn wants_key_down_for_event(_this: &Object, _sel: Sel, _event: id) -> BOOL { YES } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 34edc55161d..a745581aef2 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -1,51 +1,33 @@ -use std; -use std::cell::{Cell, RefCell}; -use std::f64; -use std::ops::Deref; -use std::os::raw::c_void; -use std::sync::{Mutex, Weak}; -use std::sync::atomic::{Ordering, AtomicBool}; - -use cocoa::appkit::{ - self, - CGFloat, - NSApp, - NSApplication, - NSColor, - NSRequestUserAttentionType, - NSScreen, - NSView, - NSWindow, - NSWindowButton, - NSWindowStyleMask, - NSApplicationActivationPolicy, - NSApplicationPresentationOptions, +use std::{ + collections::VecDeque, f64, os::raw::c_void, + sync::{Arc, atomic::{Ordering, AtomicBool}, Mutex, Weak}, }; -use cocoa::base::{id, nil}; -use cocoa::foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}; +use cocoa::{ + appkit::{ + self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy, + NSColor, NSRequestUserAttentionType, NSScreen, NSView, NSWindow, + NSWindowButton, NSWindowStyleMask, NSApplicationPresentationOptions + }, + base::{id, nil}, + foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString}, +}; use core_graphics::display::CGDisplay; - -use objc; -use objc::runtime::{Class, Object, Sel, BOOL, YES, NO}; -use objc::declare::ClassDecl; +use objc::{runtime::{Class, Object, Sel, BOOL, YES, NO}, declare::ClassDecl}; use { - CreationError, - Event, - LogicalPosition, - LogicalSize, - MouseCursor, - WindowAttributes, - WindowEvent, - WindowId, + dpi::{LogicalPosition, LogicalSize}, icon::Icon, + monitor::MonitorHandle as RootMonitorHandle, + window::{ + CreationError, MouseCursor, WindowAttributes, WindowId as RootWindowId, + }, +}; +use platform::macos::{ActivationPolicy, WindowExtMacOS}; +use platform_impl::platform::{ + app_state::AppState, ffi, monitor::{self, MonitorHandle}, + util::{self, IdRef}, view::{self, new_view}, + window_delegate::new_delegate, }; -use CreationError::OsError; -use os::macos::{ActivationPolicy, WindowExt}; -use platform_impl::platform::{ffi, util}; -use platform_impl::platform::event_loop::{EventLoop, Shared}; -use platform_impl::platform::view::{new_view, set_ime_spot}; -use window::MonitorHandle as RootMonitorHandle; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(pub usize); @@ -56,943 +38,365 @@ impl Id { } } -// TODO: It's possible for delegate methods to be called asynchronously, causing data races / `RefCell` panics. -pub struct DelegateState { - view: IdRef, - window: IdRef, - shared: Weak, - - win_attribs: RefCell, - standard_frame: Cell>, - is_simple_fullscreen: Cell, - save_style_mask: Cell>, - save_presentation_opts: Cell>, - - // This is set when WindowBuilder::with_fullscreen was set, - // see comments of `window_did_fail_to_enter_fullscreen` - handle_with_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_dpi_factor: f64, +// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier +// for the window. +pub fn get_window_id(window_cocoa_id: id) -> Id { + Id(window_cocoa_id as *const Object as _) } -impl DelegateState { - fn is_zoomed(&self) -> bool { - unsafe { - // Because isZoomed do not work in Borderless mode, we set it - // resizable temporality - let curr_mask = self.window.styleMask(); - - let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; - let needs_temp_mask = !curr_mask.contains(required); - if needs_temp_mask { - util::set_style_mask(*self.window, *self.view, required); - } - - let is_zoomed: BOOL = msg_send![*self.window, isZoomed]; - - // Roll back temp styles - if needs_temp_mask { - util::set_style_mask(*self.window, *self.view, curr_mask); - } - - is_zoomed != 0 - } - } +#[derive(Clone, Default)] +pub struct PlatformSpecificWindowBuilderAttributes { + pub activation_policy: ActivationPolicy, + pub movable_by_window_background: bool, + pub titlebar_transparent: bool, + pub title_hidden: bool, + pub titlebar_hidden: bool, + pub titlebar_buttons_hidden: bool, + pub fullsize_content_view: bool, + pub resize_increments: Option, +} - unsafe fn saved_style_mask(&self, resizable: bool) -> NSWindowStyleMask { - let base_mask = self.save_style_mask - .take() - .unwrap_or_else(|| self.window.styleMask()); - if resizable { - base_mask | NSWindowStyleMask::NSResizableWindowMask +fn create_app(activation_policy: ActivationPolicy) -> Option { + unsafe { + let nsapp = NSApp(); + if nsapp == nil { + None } else { - base_mask & !NSWindowStyleMask::NSResizableWindowMask + use self::NSApplicationActivationPolicy::*; + nsapp.setActivationPolicy_(match activation_policy { + ActivationPolicy::Regular => NSApplicationActivationPolicyRegular, + ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory, + ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited, + }); + nsapp.finishLaunching(); + Some(nsapp) } } +} - fn saved_standard_frame(&self) -> NSRect { - self.standard_frame.get().unwrap_or_else(|| NSRect::new( - NSPoint::new(50.0, 50.0), - NSSize::new(800.0, 600.0), - )) - } - - fn restore_state_from_fullscreen(&mut self) { - let maximized = unsafe { - let mut win_attribs = self.win_attribs.borrow_mut(); - win_attribs.fullscreen = None; - - let mask = self.saved_style_mask(win_attribs.resizable); - util::set_style_mask(*self.window, *self.view, mask); - - win_attribs.maximized - }; - - self.perform_maximized(maximized); - } - - fn perform_maximized(&self, maximized: bool) { - let is_zoomed = self.is_zoomed(); - - if is_zoomed == maximized { - return; - } - - // Save the standard frame sized if it is not zoomed - if !is_zoomed { - unsafe { - self.standard_frame.set(Some(NSWindow::frame(*self.window))); - } - } - - let mut win_attribs = self.win_attribs.borrow_mut(); - win_attribs.maximized = maximized; - - let curr_mask = unsafe { self.window.styleMask() }; - if win_attribs.fullscreen.is_some() { - // Handle it in window_did_exit_fullscreen - return; - } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { - // Just use the native zoom if resizable - unsafe { - self.window.zoom_(nil); - } - } else { - // if it's not resizable, we set the frame directly - unsafe { - let new_rect = if maximized { - let screen = NSScreen::mainScreen(nil); - NSScreen::visibleFrame(screen) - } else { - self.saved_standard_frame() - }; +unsafe fn create_view(nswindow: id) -> Option<(IdRef, Weak>)> { + let (nsview, cursor) = new_view(nswindow); + nsview.non_nil().map(|nsview| { + nsview.setWantsBestResolutionOpenGLSurface_(YES); - self.window.setFrame_display_(new_rect, 0); - } + // On Mojave, views automatically become layer-backed shortly after being added to + // a window. Changing the layer-backedness of a view breaks the association between + // the view and its associated OpenGL context. To work around this, on Mojave we + // explicitly make the view layer-backed up front so that AppKit doesn't do it + // itself and break the association with its context. + if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { + nsview.setWantsLayer(YES); } - } -} -pub struct WindowDelegate { - state: Box, - _this: IdRef, + nswindow.setContentView_(*nsview); + nswindow.makeFirstResponder_(*nsview); + (nsview, cursor) + }) } -impl WindowDelegate { - // Emits an event via the `EventLoop`'s callback or stores it in the pending queue. - pub fn emit_event(state: &mut DelegateState, window_event: WindowEvent) { - let window_id = get_window_id(*state.window); - let event = Event::WindowEvent { - window_id: WindowId(window_id), - event: window_event, +fn create_window( + attrs: &WindowAttributes, + pl_attrs: &PlatformSpecificWindowBuilderAttributes, +) -> Option { + unsafe { + let pool = NSAutoreleasePool::new(nil); + let screen = match attrs.fullscreen { + Some(ref monitor_id) => { + let monitor_screen = monitor_id.inner.get_nsscreen(); + Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) + }, + _ => None, + }; + let frame = match screen { + Some(screen) => appkit::NSScreen::frame(screen), + None => { + let (width, height) = attrs.dimensions + .map(|logical| (logical.width, logical.height)) + .unwrap_or_else(|| (800.0, 600.0)); + NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) + }, }; - if let Some(shared) = state.shared.upgrade() { - shared.call_user_callback_with_event_or_store_in_pending(event); - } - } - - pub fn emit_resize_event(state: &mut DelegateState) { - let rect = unsafe { NSView::frame(*state.view) }; - let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64); - WindowDelegate::emit_event(state, WindowEvent::Resized(size)); - } - - pub fn emit_move_event(state: &mut DelegateState) { - let rect = unsafe { NSWindow::frame(*state.window) }; - let x = rect.origin.x as f64; - let y = util::bottom_left_to_top_left(rect); - let moved = state.previous_position != Some((x, y)); - if moved { - state.previous_position = Some((x, y)); - WindowDelegate::emit_event(state, WindowEvent::Moved((x, y).into())); - } - } - - /// Get the delegate class, initiailizing it neccessary - fn class() -> *const Class { - use std::os::raw::c_void; - - extern fn window_should_close(this: &Object, _: Sel, _: id) -> BOOL { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::CloseRequested); - } - NO - } - - extern fn window_will_close(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - - WindowDelegate::emit_event(state, WindowEvent::Destroyed); - - // Remove the window from the shared state. - if let Some(shared) = state.shared.upgrade() { - let window_id = get_window_id(*state.window); - shared.find_and_remove_window(window_id); - } - } - } - - extern fn window_did_resize(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_resize_event(state); - WindowDelegate::emit_move_event(state); - } - } - - // This won't be triggered if the move was part of a resize. - extern fn window_did_move(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_move_event(state); - } - } - - extern fn window_did_change_screen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(state); - } - } - } - - // This will always be called before `window_did_change_screen`. - extern fn window_did_change_backing_properties(this: &Object, _:Sel, _:id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let dpi_factor = NSWindow::backingScaleFactor(*state.window) as f64; - if state.previous_dpi_factor != dpi_factor { - state.previous_dpi_factor = dpi_factor; - WindowDelegate::emit_event(state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(state); - } - } - } - - extern fn window_did_become_key(this: &Object, _: Sel, _: id) { - unsafe { - // TODO: center the cursor if the window had mouse grab when it - // lost focus - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::Focused(true)); - } - } - - extern fn window_did_resign_key(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::Focused(false)); - } - } - - /// Invoked when the dragged image enters destination bounds or frame - extern fn dragging_entered(this: &Object, _: Sel, sender: id) -> BOOL { - use cocoa::appkit::NSPasteboard; - use cocoa::foundation::NSFastEnumeration; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - 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(); - - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::HoveredFile(PathBuf::from(path))); - } - }; - - YES - } - - /// Invoked when the image is released - extern fn prepare_for_drag_operation(_: &Object, _: Sel, _: id) -> BOOL { - YES - } - - /// Invoked after the released image has been removed from the screen - extern fn perform_drag_operation(this: &Object, _: Sel, sender: id) -> BOOL { - use cocoa::appkit::NSPasteboard; - use cocoa::foundation::NSFastEnumeration; - use std::path::PathBuf; - - let pb: id = unsafe { msg_send![sender, draggingPasteboard] }; - let filenames = unsafe { NSPasteboard::propertyListForType(pb, appkit::NSFilenamesPboardType) }; - - 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(); + let mut masks = if !attrs.decorations && !screen.is_some() { + // Resizable UnownedWindow without a titlebar or borders + // if decorations is set to false, ignore pl_attrs + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + } else if pl_attrs.titlebar_hidden { + // if the titlebar is hidden, ignore other pl_attrs + NSWindowStyleMask::NSBorderlessWindowMask | + NSWindowStyleMask::NSResizableWindowMask + } else { + // default case, resizable window with titlebar and titlebar buttons + NSWindowStyleMask::NSClosableWindowMask | + NSWindowStyleMask::NSMiniaturizableWindowMask | + NSWindowStyleMask::NSResizableWindowMask | + NSWindowStyleMask::NSTitledWindowMask + }; - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::DroppedFile(PathBuf::from(path))); + if !attrs.resizable { + masks &= !NSWindowStyleMask::NSResizableWindowMask; + } + + if pl_attrs.fullsize_content_view { + masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; + } + + let nswindow: id = msg_send![WINDOW_CLASS.0, alloc]; + let nswindow = IdRef::new(nswindow.initWithContentRect_styleMask_backing_defer_( + frame, + masks, + appkit::NSBackingStoreBuffered, + NO, + )); + let res = nswindow.non_nil().map(|nswindow| { + let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); + nswindow.setReleasedWhenClosed_(NO); + nswindow.setTitle_(*title); + nswindow.setAcceptsMouseMovedEvents_(YES); + + if pl_attrs.titlebar_transparent { + nswindow.setTitlebarAppearsTransparent_(YES); + } + if pl_attrs.title_hidden { + nswindow.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); + } + if pl_attrs.titlebar_buttons_hidden { + for titlebar_button in &[ + NSWindowButton::NSWindowFullScreenButton, + NSWindowButton::NSWindowMiniaturizeButton, + NSWindowButton::NSWindowCloseButton, + NSWindowButton::NSWindowZoomButton, + ] { + let button = nswindow.standardWindowButton_(*titlebar_button); + let _: () = msg_send![button, setHidden:YES]; } - }; - - YES - } - - /// Invoked when the dragging operation is complete - extern fn conclude_drag_operation(_: &Object, _: Sel, _: id) {} - - /// Invoked when the dragging operation is cancelled - extern fn dragging_exited(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - WindowDelegate::emit_event(state, WindowEvent::HoveredFileCancelled); } - } - - /// Invoked when entered fullscreen - extern fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id){ - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - state.win_attribs.borrow_mut().fullscreen = Some(get_current_monitor(*state.window)); - - state.handle_with_fullscreen = false; + if pl_attrs.movable_by_window_background { + nswindow.setMovableByWindowBackground_(YES); } - } - - /// Invoked when before enter fullscreen - extern fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - let is_zoomed = state.is_zoomed(); - state.win_attribs.borrow_mut().maximized = is_zoomed; + if attrs.always_on_top { + let _: () = msg_send![*nswindow, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; } - } - - /// Invoked when exited fullscreen - extern fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id){ - let state = unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - &mut *(state as *mut DelegateState) - }; - - state.restore_state_from_fullscreen(); - } - /// Invoked when fail to enter fullscreen - /// - /// When this window launch from a fullscreen app (e.g. launch from VS Code - /// terminal), it creates a new virtual destkop and a transition animation. - /// This animation takes one second and cannot be disable without - /// elevated privileges. In this animation time, all toggleFullscreen events - /// will be failed. In this implementation, we will try again by using - /// performSelector:withObject:afterDelay: until window_did_enter_fullscreen. - /// It should be fine as we only do this at initialzation (i.e with_fullscreen - /// was set). - /// - /// From Apple doc: - /// In some cases, the transition to enter full-screen mode can fail, - /// due to being in the midst of handling some other animation or user gesture. - /// 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. - extern fn window_did_fail_to_enter_fullscreen(this: &Object, _: Sel, _: id) { - unsafe { - let state: *mut c_void = *this.get_ivar("winitState"); - let state = &mut *(state as *mut DelegateState); - - if state.handle_with_fullscreen { - let _: () = msg_send![*state.window, - performSelector:sel!(toggleFullScreen:) - withObject:nil - afterDelay: 0.5 - ]; - } else { - state.restore_state_from_fullscreen(); + if let Some(increments) = pl_attrs.resize_increments { + let (x, y) = (increments.width, increments.height); + if x >= 1.0 && y >= 1.0 { + let size = NSSize::new(x as CGFloat, y as CGFloat); + nswindow.setResizeIncrements_(size); } } - } - static mut DELEGATE_CLASS: *const Class = 0 as *const Class; - static INIT: std::sync::Once = std::sync::ONCE_INIT; - - INIT.call_once(|| unsafe { - // Create new NSWindowDelegate - let superclass = class!(NSObject); - let mut decl = ClassDecl::new("WinitWindowDelegate", superclass).unwrap(); - - // Add callback methods - decl.add_method(sel!(windowShouldClose:), - window_should_close as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(windowWillClose:), - window_will_close as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidResize:), - window_did_resize as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidMove:), - window_did_move as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidChangeScreen:), - window_did_change_screen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidChangeBackingProperties:), - window_did_change_backing_properties as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidBecomeKey:), - window_did_become_key as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidResignKey:), - window_did_resign_key as extern fn(&Object, Sel, id)); - - // callbacks for drag and drop events - decl.add_method(sel!(draggingEntered:), - dragging_entered as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(prepareForDragOperation:), - prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(performDragOperation:), - perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL); - decl.add_method(sel!(concludeDragOperation:), - conclude_drag_operation as extern fn(&Object, Sel, id)); - decl.add_method(sel!(draggingExited:), - dragging_exited as extern fn(&Object, Sel, id)); - - // callbacks for fullscreen events - decl.add_method(sel!(windowDidEnterFullScreen:), - window_did_enter_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowWillEnterFullScreen:), - window_will_enter_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidExitFullScreen:), - window_did_exit_fullscreen as extern fn(&Object, Sel, id)); - decl.add_method(sel!(windowDidFailToEnterFullScreen:), - window_did_fail_to_enter_fullscreen as extern fn(&Object, Sel, id)); - - // Store internal state as user data - decl.add_ivar::<*mut c_void>("winitState"); - - DELEGATE_CLASS = decl.register(); + nswindow.center(); + nswindow }); - - unsafe { - DELEGATE_CLASS - } + pool.drain(); + res } +} - fn new(state: DelegateState) -> WindowDelegate { - // Box the state so we can give a pointer to it - let mut state = Box::new(state); - let state_ptr: *mut DelegateState = &mut *state; - unsafe { - let delegate = IdRef::new(msg_send![WindowDelegate::class(), new]); - - // setDelegate uses autorelease on objects, - // so need autorelease - let autoreleasepool = NSAutoreleasePool::new(nil); - - (&mut **delegate).set_ivar("winitState", state_ptr as *mut ::std::os::raw::c_void); - let _: () = msg_send![*state.window, setDelegate:*delegate]; - - let _: () = msg_send![autoreleasepool, drain]; +struct WindowClass(*const Class); +unsafe impl Send for WindowClass {} +unsafe impl Sync for WindowClass {} + +lazy_static! { + static ref WINDOW_CLASS: WindowClass = unsafe { + let window_superclass = class!(NSWindow); + let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); + decl.add_method(sel!(canBecomeMainWindow), util::yes as extern fn(&Object, Sel) -> BOOL); + decl.add_method(sel!(canBecomeKeyWindow), util::yes as extern fn(&Object, Sel) -> BOOL); + WindowClass(decl.register()) + }; +} - WindowDelegate { state: state, _this: delegate } - } - } +#[derive(Default)] +pub struct SharedState { + pub resizable: bool, + pub fullscreen: Option, + pub maximized: bool, + standard_frame: Option, + is_simple_fullscreen: bool, + pub saved_style: Option, + save_presentation_opts: Option, } -impl Drop for WindowDelegate { - fn drop(&mut self) { - unsafe { - // Nil the window's delegate so it doesn't still reference us - // NOTE: setDelegate:nil at first retains the previous value, - // and then autoreleases it, so autorelease pool is needed - let autoreleasepool = NSAutoreleasePool::new(nil); - let _: () = msg_send![*self.state.window, setDelegate:nil]; - let _: () = msg_send![autoreleasepool, drain]; +impl From for SharedState { + fn from(attribs: WindowAttributes) -> Self { + SharedState { + resizable: attribs.resizable, + // This fullscreen field tracks the current state of the window + // (as seen by `WindowDelegate`), and since the window hasn't + // actually been fullscreened yet, we can't set it yet. This is + // necessary for state transitions to work right, since otherwise + // the initial value and the first `set_fullscreen` call would be + // identical, resulting in a no-op. + fullscreen: None, + maximized: attribs.maximized, + .. Default::default() } } } -#[derive(Clone, Default)] -pub struct PlatformSpecificWindowBuilderAttributes { - pub activation_policy: ActivationPolicy, - pub movable_by_window_background: bool, - pub titlebar_transparent: bool, - pub title_hidden: bool, - pub titlebar_hidden: bool, - pub titlebar_buttons_hidden: bool, - pub fullsize_content_view: bool, - pub resize_increments: Option, -} - -pub struct Window2 { - pub view: IdRef, - pub window: IdRef, - pub delegate: WindowDelegate, - pub input_context: IdRef, +pub struct UnownedWindow { + pub nswindow: IdRef, // never changes + pub nsview: IdRef, // never changes + input_context: IdRef, // never changes + pub shared_state: Arc>, + decorations: AtomicBool, cursor: Weak>, cursor_hidden: AtomicBool, } -unsafe impl Send for Window2 {} -unsafe impl Sync for Window2 {} - -unsafe fn get_current_monitor(window: id) -> RootMonitorHandle { - let screen: id = msg_send![window, screen]; - let desc = NSScreen::deviceDescription(screen); - let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); - let value = NSDictionary::valueForKey_(desc, *key); - let display_id = msg_send![value, unsignedIntegerValue]; - RootMonitorHandle { inner: EventLoop::make_monitor_from_display(display_id) } -} - -impl Drop for Window2 { - fn drop(&mut self) { - // Remove this window from the `EventLoop`s list of windows. - // The destructor order is: - // Window -> - // Rc (makes Weak<..> in shared.windows None) -> - // Window2 - // needed to remove the element from array - let id = self.id(); - if let Some(shared) = self.delegate.state.shared.upgrade() { - shared.find_and_remove_window(id); - } - - // nswindow::close uses autorelease - // so autorelease pool - let autoreleasepool = unsafe { - NSAutoreleasePool::new(nil) - }; - - // Close the window if it has not yet been closed. - let nswindow = *self.window; - if nswindow != nil { - unsafe { - let () = msg_send![nswindow, close]; - } - } - - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - } -} - -impl WindowExt for Window2 { - #[inline] - fn get_nswindow(&self) -> *mut c_void { - *self.window as *mut c_void - } - - #[inline] - fn get_nsview(&self) -> *mut c_void { - *self.view as *mut c_void - } - - #[inline] - fn request_user_attention(&self, is_critical: bool) { - let request_type = if is_critical { - NSRequestUserAttentionType::NSCriticalRequest - } else { - NSRequestUserAttentionType::NSInformationalRequest - }; - - unsafe { - NSApp().requestUserAttention_(request_type); - } - } - - #[inline] - fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { - let state = &self.delegate.state; - - unsafe { - let app = NSApp(); - let win_attribs = state.win_attribs.borrow_mut(); - let is_native_fullscreen = win_attribs.fullscreen.is_some(); - let is_simple_fullscreen = state.is_simple_fullscreen.get(); - - // Do nothing if native fullscreen is active. - if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) { - return false; - } - - if fullscreen { - // Remember the original window's settings - state.standard_frame.set(Some(NSWindow::frame(*self.window))); - state.save_style_mask.set(Some(self.window.styleMask())); - state.save_presentation_opts.set(Some(app.presentationOptions_())); - - // Tell our window's state that we're in fullscreen - state.is_simple_fullscreen.set(true); - - // Simulate pre-Lion fullscreen by hiding the dock and menu bar - let presentation_options = - NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | - NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; - app.setPresentationOptions_(presentation_options); - - // Hide the titlebar - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSTitledWindowMask, false); - - // Set the window frame to the screen frame size - let screen = self.window.screen(); - let screen_frame = NSScreen::frame(screen); - NSWindow::setFrame_display_(*self.window, screen_frame, YES); - - // Fullscreen windows can't be resized, minimized, or moved - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSMiniaturizableWindowMask, false); - util::toggle_style_mask(*self.window, *self.view, NSWindowStyleMask::NSResizableWindowMask, false); - NSWindow::setMovable_(*self.window, NO); - - true - } else { - let saved_style_mask = state.saved_style_mask(win_attribs.resizable); - util::set_style_mask(*self.window, *self.view, saved_style_mask); - state.is_simple_fullscreen.set(false); - - if let Some(presentation_opts) = state.save_presentation_opts.get() { - app.setPresentationOptions_(presentation_opts); - } +unsafe impl Send for UnownedWindow {} +unsafe impl Sync for UnownedWindow {} - let frame = state.saved_standard_frame(); - NSWindow::setFrame_display_(*self.window, frame, YES); - NSWindow::setMovable_(*self.window, YES); - - true - } - } - } -} - -impl Window2 { +impl UnownedWindow { pub fn new( - shared: Weak, mut win_attribs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, - ) -> Result { + ) -> Result<(Arc, IdRef), CreationError> { unsafe { if !msg_send![class!(NSThread), isMainThread] { panic!("Windows can only be created on the main thread on macOS"); } } - // Might as well save some RAM... - win_attribs.window_icon.take(); + let pool = unsafe { NSAutoreleasePool::new(nil) }; - let autoreleasepool = unsafe { - NSAutoreleasePool::new(nil) - }; + let nsapp = create_app(pl_attribs.activation_policy).ok_or_else(|| { + unsafe { pool.drain() }; + CreationError::OsError(format!("Couldn't create `NSApplication`")) + })?; - let app = match Window2::create_app(pl_attribs.activation_policy) { - Some(app) => app, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSApplication"))); - }, - }; + let nswindow = create_window(&win_attribs, &pl_attribs).ok_or_else(|| { + unsafe { pool.drain() }; + CreationError::OsError(format!("Couldn't create `NSWindow`")) + })?; - let window = match Window2::create_window(&win_attribs, &pl_attribs) { - Some(res) => res, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSWindow"))); - }, - }; - let (view, cursor) = match Window2::create_view(*window, Weak::clone(&shared)) { - Some(view) => view, - None => { - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - return Err(OsError(format!("Couldn't create NSView"))); - }, - }; + let (nsview, cursor) = unsafe { create_view(*nswindow) }.ok_or_else(|| { + unsafe { pool.drain() }; + CreationError::OsError(format!("Couldn't create `NSView`")) + })?; - let input_context = unsafe { util::create_input_context(*view) }; + let input_context = unsafe { util::create_input_context(*nsview) }; unsafe { if win_attribs.transparent { - (*window as id).setOpaque_(NO); - (*window as id).setBackgroundColor_(NSColor::clearColor(nil)); + nswindow.setOpaque_(NO); + nswindow.setBackgroundColor_(NSColor::clearColor(nil)); } - app.activateIgnoringOtherApps_(YES); + nsapp.activateIgnoringOtherApps_(YES); - if let Some(dimensions) = win_attribs.min_dimensions { - nswindow_set_min_dimensions(window.0, dimensions); - } - if let Some(dimensions) = win_attribs.max_dimensions { - nswindow_set_max_dimensions(window.0, dimensions); - } + win_attribs.min_dimensions.map(|dim| set_min_dimensions(*nswindow, dim)); + win_attribs.max_dimensions.map(|dim| set_max_dimensions(*nswindow, dim)); use cocoa::foundation::NSArray; // register for drag and drop operations. - let () = msg_send![(*window as id), - registerForDraggedTypes:NSArray::arrayWithObject(nil, appkit::NSFilenamesPboardType)]; - } - - let dpi_factor = unsafe { NSWindow::backingScaleFactor(*window) as f64 }; - - let mut delegate_state = DelegateState { - view: view.clone(), - window: window.clone(), - shared, - win_attribs: RefCell::new(win_attribs.clone()), - standard_frame: Cell::new(None), - is_simple_fullscreen: Cell::new(false), - save_style_mask: Cell::new(None), - save_presentation_opts: Cell::new(None), - handle_with_fullscreen: win_attribs.fullscreen.is_some(), - previous_position: None, - previous_dpi_factor: dpi_factor, - }; - delegate_state.win_attribs.borrow_mut().fullscreen = None; - - if dpi_factor != 1.0 { - WindowDelegate::emit_event(&mut delegate_state, WindowEvent::HiDpiFactorChanged(dpi_factor)); - WindowDelegate::emit_resize_event(&mut delegate_state); - } - - let window = Window2 { - view: view, - window: window, - delegate: WindowDelegate::new(delegate_state), + let () = msg_send![*nswindow, registerForDraggedTypes:NSArray::arrayWithObject( + nil, + appkit::NSFilenamesPboardType, + )]; + } + + // Since `win_attribs` is put into a mutex below, we'll just copy these + // attributes now instead of bothering to lock it later. + // Also, `SharedState` doesn't carry `fullscreen` over; it's set + // indirectly by us calling `set_fullscreen` below, causing handlers in + // `WindowDelegate` to update the state. + let fullscreen = win_attribs.fullscreen.take(); + let maximized = win_attribs.maximized; + let visible = win_attribs.visible; + let decorations = win_attribs.decorations; + + let window = Arc::new(UnownedWindow { + nsview, + nswindow, input_context, + shared_state: Arc::new(Mutex::new(win_attribs.into())), + decorations: AtomicBool::new(decorations), cursor, cursor_hidden: Default::default(), - }; + }); + + let delegate = new_delegate(&window, fullscreen.is_some()); // Set fullscreen mode after we setup everything - if let Some(ref monitor) = win_attribs.fullscreen { - unsafe { - if monitor.inner != get_current_monitor(*window.window).inner { - unimplemented!(); - } + if let Some(monitor) = fullscreen { + if monitor.inner != window.get_current_monitor().inner { + // To do this with native fullscreen, we probably need to + // warp the window... while we could use + // `enterFullScreenMode`, they're idiomatically different + // fullscreen modes, so we'd have to support both anyway. + unimplemented!(); } - window.set_fullscreen(Some(monitor.clone())); + window.set_fullscreen(Some(monitor)); } - // Make key have to be after set fullscreen - // to prevent normal size window brefly appears + // Setting the window as key has to happen *after* we set the fullscreen + // state, since otherwise we'll briefly see the window at normal size + // before it transitions. unsafe { - if win_attribs.visible { - window.window.makeKeyAndOrderFront_(nil); + if visible { + window.nswindow.makeKeyAndOrderFront_(nil); } else { - window.window.makeKeyWindow(); + window.nswindow.makeKeyWindow(); } } - if win_attribs.maximized { - window.delegate.state.perform_maximized(win_attribs.maximized); + if maximized { + window.set_maximized(maximized); } - let _: () = unsafe { msg_send![autoreleasepool, drain] }; - - Ok(window) - } - - pub fn id(&self) -> Id { - get_window_id(*self.window) - } + unsafe { pool.drain() }; - fn create_app(activation_policy: ActivationPolicy) -> Option { - unsafe { - let app = appkit::NSApp(); - if app == nil { - None - } else { - let ns_activation_policy = match activation_policy { - ActivationPolicy::Regular => - NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular, - ActivationPolicy::Accessory => - NSApplicationActivationPolicy::NSApplicationActivationPolicyAccessory, - ActivationPolicy::Prohibited => - NSApplicationActivationPolicy::NSApplicationActivationPolicyProhibited, - }; - app.setActivationPolicy_(ns_activation_policy); - app.finishLaunching(); - Some(app) - } - } + Ok((window, delegate)) } - fn class() -> *const Class { - static mut WINDOW2_CLASS: *const Class = 0 as *const Class; - static INIT: std::sync::Once = std::sync::ONCE_INIT; - - INIT.call_once(|| unsafe { - let window_superclass = class!(NSWindow); - let mut decl = ClassDecl::new("WinitWindow", window_superclass).unwrap(); - decl.add_method(sel!(canBecomeMainWindow), yes as extern fn(&Object, Sel) -> BOOL); - decl.add_method(sel!(canBecomeKeyWindow), yes as extern fn(&Object, Sel) -> BOOL); - WINDOW2_CLASS = decl.register(); - }); - - unsafe { - WINDOW2_CLASS - } + fn set_style_mask_async(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_async( + *self.nswindow, + *self.nsview, + mask, + ) }; } - fn create_window( - attrs: &WindowAttributes, - pl_attrs: &PlatformSpecificWindowBuilderAttributes - ) -> Option { - unsafe { - let autoreleasepool = NSAutoreleasePool::new(nil); - let screen = match attrs.fullscreen { - Some(ref monitor_id) => { - let monitor_screen = monitor_id.inner.get_nsscreen(); - Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil))) - }, - _ => None, - }; - let frame = match screen { - Some(screen) => appkit::NSScreen::frame(screen), - None => { - let (width, height) = attrs.dimensions - .map(|logical| (logical.width, logical.height)) - .unwrap_or((800.0, 600.0)); - NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(width, height)) - } - }; - - let mut masks = if !attrs.decorations && !screen.is_some() { - // Resizable Window2 without a titlebar or borders - // if decorations is set to false, ignore pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask - | NSWindowStyleMask::NSResizableWindowMask - } else if pl_attrs.titlebar_hidden { - // if the titlebar is hidden, ignore other pl_attrs - NSWindowStyleMask::NSBorderlessWindowMask | - NSWindowStyleMask::NSResizableWindowMask - } else { - // default case, resizable window with titlebar and titlebar buttons - NSWindowStyleMask::NSClosableWindowMask | - NSWindowStyleMask::NSMiniaturizableWindowMask | - NSWindowStyleMask::NSResizableWindowMask | - NSWindowStyleMask::NSTitledWindowMask - }; - - if !attrs.resizable { - masks &= !NSWindowStyleMask::NSResizableWindowMask; - } - - if pl_attrs.fullsize_content_view { - masks |= NSWindowStyleMask::NSFullSizeContentViewWindowMask; - } - - let winit_window = Window2::class(); - - let window: id = msg_send![winit_window, alloc]; - - let window = IdRef::new(window.initWithContentRect_styleMask_backing_defer_( - frame, - masks, - appkit::NSBackingStoreBuffered, - NO, - )); - let res = window.non_nil().map(|window| { - let title = IdRef::new(NSString::alloc(nil).init_str(&attrs.title)); - window.setReleasedWhenClosed_(NO); - window.setTitle_(*title); - window.setAcceptsMouseMovedEvents_(YES); - - if pl_attrs.titlebar_transparent { - window.setTitlebarAppearsTransparent_(YES); - } - if pl_attrs.title_hidden { - window.setTitleVisibility_(appkit::NSWindowTitleVisibility::NSWindowTitleHidden); - } - if pl_attrs.titlebar_buttons_hidden { - let button = window.standardWindowButton_(NSWindowButton::NSWindowFullScreenButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); - let () = msg_send![button, setHidden:YES]; - let button = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); - let () = msg_send![button, setHidden:YES]; - } - if pl_attrs.movable_by_window_background { - window.setMovableByWindowBackground_(YES); - } - - if attrs.always_on_top { - let _: () = msg_send![*window, setLevel:ffi::NSWindowLevel::NSFloatingWindowLevel]; - } - - if let Some(increments) = pl_attrs.resize_increments { - let (x, y) = (increments.width, increments.height); - if x >= 1.0 && y >= 1.0 { - let size = NSSize::new(x as CGFloat, y as CGFloat); - window.setResizeIncrements_(size); - } - } - - window.center(); - window - }); - let _: () = msg_send![autoreleasepool, drain]; - res - } + fn set_style_mask_sync(&self, mask: NSWindowStyleMask) { + unsafe { util::set_style_mask_sync( + *self.nswindow, + *self.nsview, + mask, + ) }; } - fn create_view(window: id, shared: Weak) -> Option<(IdRef, Weak>)> { - unsafe { - let (view, cursor) = new_view(window, shared); - view.non_nil().map(|view| { - view.setWantsBestResolutionOpenGLSurface_(YES); - - // On Mojave, views automatically become layer-backed shortly after being added to - // a window. Changing the layer-backedness of a view breaks the association between - // the view and its associated OpenGL context. To work around this, on Mojave we - // explicitly make the view layer-backed up front so that AppKit doesn't do it - // itself and break the association with its context. - if f64::floor(appkit::NSAppKitVersionNumber) > appkit::NSAppKitVersionNumber10_12 { - view.setWantsLayer(YES); - } - - window.setContentView_(*view); - window.makeFirstResponder_(*view); - (view, cursor) - }) - } + pub fn id(&self) -> Id { + get_window_id(*self.nswindow) } pub fn set_title(&self, title: &str) { unsafe { let title = IdRef::new(NSString::alloc(nil).init_str(title)); - self.window.setTitle_(*title); + self.nswindow.setTitle_(*title); } } #[inline] pub fn show(&self) { - unsafe { NSWindow::makeKeyAndOrderFront_(*self.window, nil); } + unsafe { util::make_key_and_order_front_async(*self.nswindow) }; } #[inline] pub fn hide(&self) { - unsafe { NSWindow::orderOut_(*self.window, nil); } + unsafe { util::order_out_async(*self.nswindow) }; + } + + pub fn request_redraw(&self) { + AppState::queue_redraw(RootWindowId(self.id())); } pub fn get_position(&self) -> Option { - let frame_rect = unsafe { NSWindow::frame(*self.window) }; + let frame_rect = unsafe { NSWindow::frame(*self.nswindow) }; Some(( frame_rect.origin.x as f64, util::bottom_left_to_top_left(frame_rect), @@ -1002,8 +406,8 @@ impl Window2 { pub fn get_inner_position(&self) -> Option { let content_rect = unsafe { NSWindow::contentRectForFrameRect_( - *self.window, - NSWindow::frame(*self.window), + *self.nswindow, + NSWindow::frame(*self.nswindow), ) }; Some(( @@ -1016,62 +420,67 @@ impl Window2 { let dummy = NSRect::new( NSPoint::new( position.x, - // While it's true that we're setting the top-left position, it still needs to be - // in a bottom-left coordinate system. + // While it's true that we're setting the top-left position, + // it still needs to be in a bottom-left coordinate system. CGDisplay::main().pixels_high() as f64 - position.y, ), NSSize::new(0f64, 0f64), ); unsafe { - NSWindow::setFrameTopLeftPoint_(*self.window, dummy.origin); + util::set_frame_top_left_point_async(*self.nswindow, dummy.origin); } } #[inline] pub fn get_inner_size(&self) -> Option { - let view_frame = unsafe { NSView::frame(*self.view) }; + let view_frame = unsafe { NSView::frame(*self.nsview) }; Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) } #[inline] pub fn get_outer_size(&self) -> Option { - let view_frame = unsafe { NSWindow::frame(*self.window) }; + let view_frame = unsafe { NSWindow::frame(*self.nswindow) }; Some((view_frame.size.width as f64, view_frame.size.height as f64).into()) } #[inline] pub fn set_inner_size(&self, size: LogicalSize) { unsafe { - NSWindow::setContentSize_(*self.window, NSSize::new(size.width as CGFloat, size.height as CGFloat)); + util::set_content_size_async(*self.nswindow, size); } } pub fn set_min_dimensions(&self, dimensions: Option) { unsafe { let dimensions = dimensions.unwrap_or_else(|| (0, 0).into()); - nswindow_set_min_dimensions(self.window.0, dimensions); + set_min_dimensions(*self.nswindow, dimensions); } } pub fn set_max_dimensions(&self, dimensions: Option) { unsafe { let dimensions = dimensions.unwrap_or_else(|| (!0, !0).into()); - nswindow_set_max_dimensions(self.window.0, dimensions); + set_max_dimensions(*self.nswindow, dimensions); } } #[inline] pub fn set_resizable(&self, resizable: bool) { - let mut win_attribs = self.delegate.state.win_attribs.borrow_mut(); - win_attribs.resizable = resizable; - if win_attribs.fullscreen.is_none() { - let mut mask = unsafe { self.window.styleMask() }; + let fullscreen = { + trace!("Locked shared state in `set_resizable`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + shared_state_lock.resizable = resizable; + trace!("Unlocked shared state in `set_resizable`"); + shared_state_lock.fullscreen.is_some() + }; + if !fullscreen { + let mut mask = unsafe { self.nswindow.styleMask() }; if resizable { mask |= NSWindowStyleMask::NSResizableWindowMask; } else { mask &= !NSWindowStyleMask::NSResizableWindowMask; } - unsafe { util::set_style_mask(*self.window, *self.view, mask) }; + self.set_style_mask_async(mask); } // Otherwise, we don't change the mask until we exit fullscreen. } @@ -1081,8 +490,8 @@ impl Window2 { *cursor_access.lock().unwrap() = cursor; } unsafe { - let _: () = msg_send![*self.window, - invalidateCursorRectsForView:*self.view + let _: () = msg_send![*self.nswindow, + invalidateCursorRectsForView:*self.nsview ]; } } @@ -1111,9 +520,7 @@ impl Window2 { #[inline] pub fn get_hidpi_factor(&self) -> f64 { - unsafe { - NSWindow::backingScaleFactor(*self.window) as f64 - } + unsafe { NSWindow::backingScaleFactor(*self.nswindow) as _ } } #[inline] @@ -1132,136 +539,325 @@ impl Window2 { Ok(()) } + pub(crate) fn is_zoomed(&self) -> bool { + // because `isZoomed` doesn't work if the window's borderless, + // we make it resizable temporalily. + let curr_mask = unsafe { self.nswindow.styleMask() }; + + let required = NSWindowStyleMask::NSTitledWindowMask + | NSWindowStyleMask::NSResizableWindowMask; + let needs_temp_mask = !curr_mask.contains(required); + if needs_temp_mask { + self.set_style_mask_sync(required); + } + + let is_zoomed: BOOL = unsafe { msg_send![*self.nswindow, isZoomed] }; + + // Roll back temp styles + if needs_temp_mask { + self.set_style_mask_async(curr_mask); + } + + is_zoomed != 0 + } + + fn saved_style(&self, shared_state: &mut SharedState) -> NSWindowStyleMask { + let base_mask = shared_state.saved_style + .take() + .unwrap_or_else(|| unsafe { self.nswindow.styleMask() }); + if shared_state.resizable { + base_mask | NSWindowStyleMask::NSResizableWindowMask + } else { + base_mask & !NSWindowStyleMask::NSResizableWindowMask + } + } + + fn saved_standard_frame(shared_state: &mut SharedState) -> NSRect { + shared_state.standard_frame.unwrap_or_else(|| NSRect::new( + NSPoint::new(50.0, 50.0), + NSSize::new(800.0, 600.0), + )) + } + + pub(crate) fn restore_state_from_fullscreen(&self) { + let maximized = { + trace!("Locked shared state in `restore_state_from_fullscreen`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + shared_state_lock.fullscreen = None; + + let mask = self.saved_style(&mut *shared_state_lock); + + self.set_style_mask_async(mask); + shared_state_lock.maximized + }; + trace!("Unocked shared state in `restore_state_from_fullscreen`"); + self.set_maximized(maximized); + } + #[inline] pub fn set_maximized(&self, maximized: bool) { - self.delegate.state.perform_maximized(maximized) + let is_zoomed = self.is_zoomed(); + if is_zoomed == maximized { return }; + + trace!("Locked shared state in `set_maximized`"); + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + // Save the standard frame sized if it is not zoomed + if !is_zoomed { + unsafe { + shared_state_lock.standard_frame = Some(NSWindow::frame(*self.nswindow)); + } + } + + shared_state_lock.maximized = maximized; + + let curr_mask = unsafe { self.nswindow.styleMask() }; + if shared_state_lock.fullscreen.is_some() { + // Handle it in window_did_exit_fullscreen + return; + } else if curr_mask.contains(NSWindowStyleMask::NSResizableWindowMask) { + // Just use the native zoom if resizable + unsafe { self.nswindow.zoom_(nil) }; + } else { + // if it's not resizable, we set the frame directly + unsafe { + let new_rect = if maximized { + let screen = NSScreen::mainScreen(nil); + NSScreen::visibleFrame(screen) + } else { + Self::saved_standard_frame(&mut *shared_state_lock) + }; + // This probably isn't thread-safe! + self.nswindow.setFrame_display_(new_rect, 0); + } + } + + trace!("Unlocked shared state in `set_maximized`"); } + // TODO: `set_fullscreen` is only usable if you fullscreen on the same + // monitor the window's currently on. #[inline] - /// TODO: Right now set_fullscreen do not work on switching monitors - /// in fullscreen mode pub fn set_fullscreen(&self, monitor: Option) { - let state = &self.delegate.state; - - // Do nothing if simple fullscreen is active. - if state.is_simple_fullscreen.get() { + let shared_state_lock = self.shared_state.lock().unwrap(); + if shared_state_lock.is_simple_fullscreen { return } - let current = { - let win_attribs = state.win_attribs.borrow_mut(); - - let current = win_attribs.fullscreen.clone(); - match (¤t, monitor) { - (&None, None) => { - return; - } + let not_fullscreen = { + trace!("Locked shared state in `set_fullscreen`"); + let current = &shared_state_lock.fullscreen; + match (current, monitor) { (&Some(ref a), Some(ref b)) if a.inner != b.inner => { - unimplemented!(); - } - (&Some(_), Some(_)) => { - return; - } + // Our best bet is probably to move to the origin of the + // target monitor. + unimplemented!() + }, + (&None, None) | (&Some(_), Some(_)) => return, _ => (), } - - current + trace!("Unlocked shared state in `set_fullscreen`"); + current.is_none() }; - unsafe { - // Because toggleFullScreen will not work if the StyleMask is none, - // We set a normal style to it temporary. - // It will clean up at window_did_exit_fullscreen. - if current.is_none() { - let curr_mask = state.window.styleMask(); - let required = NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask; - if !curr_mask.contains(required) { - util::set_style_mask(*self.window, *self.view, required); - state.save_style_mask.set(Some(curr_mask)); - } - } - - self.window.toggleFullScreen_(nil); - } + unsafe { util::toggle_full_screen_async( + *self.nswindow, + *self.nsview, + not_fullscreen, + Arc::downgrade(&self.shared_state), + ) }; } #[inline] pub fn set_decorations(&self, decorations: bool) { - let state = &self.delegate.state; - let mut win_attribs = state.win_attribs.borrow_mut(); + if decorations != self.decorations.load(Ordering::Acquire) { + self.decorations.store(decorations, Ordering::Release); + + let (fullscreen, resizable) = { + trace!("Locked shared state in `set_decorations`"); + let shared_state_lock = self.shared_state.lock().unwrap(); + trace!("Unlocked shared state in `set_decorations`"); + ( + shared_state_lock.fullscreen.is_some(), + shared_state_lock.resizable, + ) + }; - if win_attribs.decorations == decorations { - return; + // If we're in fullscreen mode, we wait to apply decoration changes + // until we're in `window_did_exit_fullscreen`. + if fullscreen { return } + + let new_mask = { + let mut new_mask = if decorations { + NSWindowStyleMask::NSClosableWindowMask + | NSWindowStyleMask::NSMiniaturizableWindowMask + | NSWindowStyleMask::NSResizableWindowMask + | NSWindowStyleMask::NSTitledWindowMask + } else { + NSWindowStyleMask::NSBorderlessWindowMask + | NSWindowStyleMask::NSResizableWindowMask + }; + if !resizable { + new_mask &= !NSWindowStyleMask::NSResizableWindowMask; + } + new_mask + }; + self.set_style_mask_async(new_mask); } + } - win_attribs.decorations = decorations; + #[inline] + pub fn set_always_on_top(&self, always_on_top: bool) { + let level = if always_on_top { + ffi::NSWindowLevel::NSFloatingWindowLevel + } else { + ffi::NSWindowLevel::NSNormalWindowLevel + }; + unsafe { util::set_level_async(*self.nswindow, level) }; + } - // Skip modifiy if we are in fullscreen mode, - // window_did_exit_fullscreen will handle it - if win_attribs.fullscreen.is_some() { - return; - } + #[inline] + pub fn set_window_icon(&self, _icon: Option) { + // macOS doesn't have window icons. Though, there is + // `setRepresentedFilename`, but that's semantically distinct and should + // only be used when the window is in some way representing a specific + // file/directory. For instance, Terminal.app uses this for the CWD. + // Anyway, that should eventually be implemented as + // `WindowBuilderExt::with_represented_file` or something, and doesn't + // have anything to do with `set_window_icon`. + // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + } + #[inline] + pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { unsafe { - let mut new_mask = if decorations { - NSWindowStyleMask::NSClosableWindowMask - | NSWindowStyleMask::NSMiniaturizableWindowMask - | NSWindowStyleMask::NSResizableWindowMask - | NSWindowStyleMask::NSTitledWindowMask - } else { - NSWindowStyleMask::NSBorderlessWindowMask - | NSWindowStyleMask::NSResizableWindowMask - }; - if !win_attribs.resizable { - new_mask &= !NSWindowStyleMask::NSResizableWindowMask; - } - util::set_style_mask(*state.window, *state.view, new_mask); + view::set_ime_spot( + *self.nsview, + *self.input_context, + logical_spot.x, + logical_spot.y, + ); } } #[inline] - pub fn set_always_on_top(&self, always_on_top: bool) { + pub fn get_current_monitor(&self) -> RootMonitorHandle { unsafe { - let level = if always_on_top { - ffi::NSWindowLevel::NSFloatingWindowLevel - } else { - ffi::NSWindowLevel::NSNormalWindowLevel - }; - let _: () = msg_send![*self.window, setLevel:level]; + let screen: id = msg_send![*self.nswindow, screen]; + let desc = NSScreen::deviceDescription(screen); + let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber")); + let value = NSDictionary::valueForKey_(desc, *key); + let display_id = msg_send![value, unsignedIntegerValue]; + RootMonitorHandle { inner: MonitorHandle::new(display_id) } } } #[inline] - pub fn set_window_icon(&self, _icon: Option<::Icon>) { - // macOS doesn't have window icons. Though, there is `setRepresentedFilename`, but that's - // semantically distinct and should only be used when the window is in some way - // representing a specific file/directory. For instance, Terminal.app uses this for the - // CWD. Anyway, that should eventually be implemented as - // `WindowBuilderExt::with_represented_file` or something, and doesn't have anything to do - // with `set_window_icon`. - // https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/WinPanel/Tasks/SettingWindowTitle.html + pub fn get_available_monitors(&self) -> VecDeque { + monitor::get_available_monitors() } #[inline] - pub fn set_ime_spot(&self, logical_spot: LogicalPosition) { - set_ime_spot(*self.view, *self.input_context, logical_spot.x, logical_spot.y); + pub fn get_primary_monitor(&self) -> MonitorHandle { + monitor::get_primary_monitor() } +} +impl WindowExtMacOS for UnownedWindow { #[inline] - pub fn get_current_monitor(&self) -> RootMonitorHandle { + fn get_nswindow(&self) -> *mut c_void { + *self.nswindow as *mut _ + } + + #[inline] + fn get_nsview(&self) -> *mut c_void { + *self.nsview as *mut _ + } + + #[inline] + fn request_user_attention(&self, is_critical: bool) { unsafe { - self::get_current_monitor(*self.window) + NSApp().requestUserAttention_(match is_critical { + true => NSRequestUserAttentionType::NSCriticalRequest, + false => NSRequestUserAttentionType::NSInformationalRequest, + }); + } + } + + #[inline] + fn set_simple_fullscreen(&self, fullscreen: bool) -> bool { + let mut shared_state_lock = self.shared_state.lock().unwrap(); + + unsafe { + let app = NSApp(); + let is_native_fullscreen = shared_state_lock.fullscreen.is_some(); + let is_simple_fullscreen = shared_state_lock.is_simple_fullscreen; + + // Do nothing if native fullscreen is active. + if is_native_fullscreen || (fullscreen && is_simple_fullscreen) || (!fullscreen && !is_simple_fullscreen) { + return false; + } + + if fullscreen { + // Remember the original window's settings + shared_state_lock.standard_frame = Some(NSWindow::frame(*self.nswindow)); + shared_state_lock.saved_style = Some(self.nswindow.styleMask()); + shared_state_lock.save_presentation_opts = Some(app.presentationOptions_()); + + // Tell our window's state that we're in fullscreen + shared_state_lock.is_simple_fullscreen = true; + + // Simulate pre-Lion fullscreen by hiding the dock and menu bar + let presentation_options = + NSApplicationPresentationOptions::NSApplicationPresentationAutoHideDock | + NSApplicationPresentationOptions::NSApplicationPresentationAutoHideMenuBar; + app.setPresentationOptions_(presentation_options); + + // Hide the titlebar + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSTitledWindowMask, false); + + // Set the window frame to the screen frame size + let screen = self.nswindow.screen(); + let screen_frame = NSScreen::frame(screen); + NSWindow::setFrame_display_(*self.nswindow, screen_frame, YES); + + // Fullscreen windows can't be resized, minimized, or moved + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSMiniaturizableWindowMask, false); + util::toggle_style_mask(*self.nswindow, *self.nsview, NSWindowStyleMask::NSResizableWindowMask, false); + NSWindow::setMovable_(*self.nswindow, NO); + + true + } else { + let new_mask = self.saved_style(&mut *shared_state_lock); + self.set_style_mask_async(new_mask); + shared_state_lock.is_simple_fullscreen = false; + + if let Some(presentation_opts) = shared_state_lock.save_presentation_opts { + app.setPresentationOptions_(presentation_opts); + } + + let frame = Self::saved_standard_frame(&mut *shared_state_lock); + NSWindow::setFrame_display_(*self.nswindow, frame, YES); + NSWindow::setMovable_(*self.nswindow, YES); + + true + } } } } -// Convert the `cocoa::base::id` associated with a window to a usize to use as a unique identifier -// for the window. -pub fn get_window_id(window_cocoa_id: id) -> Id { - Id(window_cocoa_id as *const objc::runtime::Object as usize) +impl Drop for UnownedWindow { + fn drop(&mut self) { + trace!("Dropping `UnownedWindow` ({:?})", self as *mut _); + // Close the window if it has not yet been closed. + if *self.nswindow != nil { + unsafe { util::close_async(*self.nswindow) }; + } + } } -unsafe fn nswindow_set_min_dimensions(window: V, mut min_size: LogicalSize) { +unsafe fn set_min_dimensions(window: V, mut min_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size @@ -1285,7 +881,7 @@ unsafe fn nswindow_set_min_dimensions(window: V, mut min_siz } } -unsafe fn nswindow_set_max_dimensions(window: V, mut max_size: LogicalSize) { +unsafe fn set_max_dimensions(window: V, mut max_size: LogicalSize) { let mut current_rect = NSWindow::frame(window); let content_rect = NSWindow::contentRectForFrameRect_(window, NSWindow::frame(window)); // Convert from client area size to window size @@ -1308,55 +904,3 @@ unsafe fn nswindow_set_max_dimensions(window: V, mut max_siz window.setFrame_display_(current_rect, 0) } } - -pub struct IdRef(id); - -impl IdRef { - pub fn new(i: id) -> IdRef { - IdRef(i) - } - - #[allow(dead_code)] - pub fn retain(i: id) -> IdRef { - if i != nil { - let _: id = unsafe { msg_send![i, retain] }; - } - IdRef(i) - } - - pub fn non_nil(self) -> Option { - if self.0 == nil { None } else { Some(self) } - } -} - -impl Drop for IdRef { - fn drop(&mut self) { - if self.0 != nil { - unsafe { - let autoreleasepool = NSAutoreleasePool::new(nil); - let _ : () = msg_send![self.0, release]; - let _ : () = msg_send![autoreleasepool, release]; - }; - } - } -} - -impl Deref for IdRef { - type Target = id; - fn deref<'a>(&'a self) -> &'a id { - &self.0 - } -} - -impl Clone for IdRef { - fn clone(&self) -> IdRef { - if self.0 != nil { - let _: id = unsafe { msg_send![self.0, retain] }; - } - IdRef(self.0) - } -} - -extern fn yes(_: &Object, _: Sel) -> BOOL { - YES -} diff --git a/src/util.rs b/src/util.rs index 6d50fb13885..e69de29bb2d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,12 +0,0 @@ -use std::ops::BitAnd; - -// Replace with `!` once stable -#[derive(Debug)] -pub enum Never {} - -pub fn has_flag(bitset: T, flag: T) -> bool -where T: - Copy + PartialEq + BitAnd -{ - bitset & flag == flag -} From 742a688efe2f0eeacc2ffbf49b1157c4aaffccbd Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Wed, 24 Apr 2019 23:09:14 -0600 Subject: [PATCH 3/6] Backports https://github.com/rust-windowing/winit/commit/45a428141329377a1669698e4ca6561834c0dbcc#diff-1d95fe39cdbaa708c975380a16c314cb Signed-off-by: Hal Gentz --- src/platform_impl/macos/event.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index 10cd876f81d..a488ebb0bea 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -103,8 +103,8 @@ pub fn to_virtual_keycode(scancode: c_ushort) -> Option { 0x5a => VirtualKeyCode::F20, 0x5b => VirtualKeyCode::Numpad8, 0x5c => VirtualKeyCode::Numpad9, - //0x5d => unkown, - //0x5e => unkown, + 0x5d => VirtualKeyCode::Yen, + //0x5e => JIS Ro, //0x5f => unkown, 0x60 => VirtualKeyCode::F5, 0x61 => VirtualKeyCode::F6, @@ -112,9 +112,9 @@ pub fn to_virtual_keycode(scancode: c_ushort) -> Option { 0x63 => VirtualKeyCode::F3, 0x64 => VirtualKeyCode::F8, 0x65 => VirtualKeyCode::F9, - //0x66 => unkown, + //0x66 => JIS Eisuu (macOS), 0x67 => VirtualKeyCode::F11, - //0x68 => unkown, + //0x68 => JIS Kanna (macOS), 0x69 => VirtualKeyCode::F13, 0x6a => VirtualKeyCode::F16, 0x6b => VirtualKeyCode::F14, From 86bc86f3d3add4a6125aa9b2eca79061c0dfcd91 Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Wed, 24 Apr 2019 23:39:19 -0600 Subject: [PATCH 4/6] Backport https://github.com/rust-windowing/winit/commit/9a23ec3c373d096e5e9dd0cc0a23f7baf0b64135#diff-1d95fe39cdbaa708c975380a16c314cb Signed-off-by: Hal Gentz --- src/platform_impl/macos/event.rs | 101 ++++++++++++++++++++++++++----- src/platform_impl/macos/view.rs | 85 +++++++++++++++----------- 2 files changed, 136 insertions(+), 50 deletions(-) diff --git a/src/platform_impl/macos/event.rs b/src/platform_impl/macos/event.rs index a488ebb0bea..c19c5300def 100644 --- a/src/platform_impl/macos/event.rs +++ b/src/platform_impl/macos/event.rs @@ -8,7 +8,67 @@ use event::{ }; use platform_impl::platform::DEVICE_ID; -pub fn to_virtual_keycode(scancode: c_ushort) -> Option { +pub fn char_to_keycode(c: char) -> Option { + // We only translate keys that are affected by keyboard layout. + // + // Note that since keys are translated in a somewhat "dumb" way (reading character) + // there is a concern that some combination, i.e. Cmd+char, causes the wrong + // letter to be received, and so we receive the wrong key. + // + // Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626 + Some(match c { + 'a' | 'A' => VirtualKeyCode::A, + 'b' | 'B' => VirtualKeyCode::B, + 'c' | 'C' => VirtualKeyCode::C, + 'd' | 'D' => VirtualKeyCode::D, + 'e' | 'E' => VirtualKeyCode::E, + 'f' | 'F' => VirtualKeyCode::F, + 'g' | 'G' => VirtualKeyCode::G, + 'h' | 'H' => VirtualKeyCode::H, + 'i' | 'I' => VirtualKeyCode::I, + 'j' | 'J' => VirtualKeyCode::J, + 'k' | 'K' => VirtualKeyCode::K, + 'l' | 'L' => VirtualKeyCode::L, + 'm' | 'M' => VirtualKeyCode::M, + 'n' | 'N' => VirtualKeyCode::N, + 'o' | 'O' => VirtualKeyCode::O, + 'p' | 'P' => VirtualKeyCode::P, + 'q' | 'Q' => VirtualKeyCode::Q, + 'r' | 'R' => VirtualKeyCode::R, + 's' | 'S' => VirtualKeyCode::S, + 't' | 'T' => VirtualKeyCode::T, + 'u' | 'U' => VirtualKeyCode::U, + 'v' | 'V' => VirtualKeyCode::V, + 'w' | 'W' => VirtualKeyCode::W, + 'x' | 'X' => VirtualKeyCode::X, + 'y' | 'Y' => VirtualKeyCode::Y, + 'z' | 'Z' => VirtualKeyCode::Z, + '1' | '!' => VirtualKeyCode::Key1, + '2' | '@' => VirtualKeyCode::Key2, + '3' | '#' => VirtualKeyCode::Key3, + '4' | '$' => VirtualKeyCode::Key4, + '5' | '%' => VirtualKeyCode::Key5, + '6' | '^' => VirtualKeyCode::Key6, + '7' | '&' => VirtualKeyCode::Key7, + '8' | '*' => VirtualKeyCode::Key8, + '9' | '(' => VirtualKeyCode::Key9, + '0' | ')' => VirtualKeyCode::Key0, + '=' | '+' => VirtualKeyCode::Equals, + '-' | '_' => VirtualKeyCode::Minus, + ']' | '}' => VirtualKeyCode::RBracket, + '[' | '{' => VirtualKeyCode::LBracket, + '\''| '"' => VirtualKeyCode::Apostrophe, + ';' | ':' => VirtualKeyCode::Semicolon, + '\\'| '|' => VirtualKeyCode::Backslash, + ',' | '<' => VirtualKeyCode::Comma, + '/' | '?' => VirtualKeyCode::Slash, + '.' | '>' => VirtualKeyCode::Period, + '`' | '~' => VirtualKeyCode::Grave, + _ => return None, + }) +} + +pub fn scancode_to_keycode(scancode: c_ushort) -> Option { Some(match scancode { 0x00 => VirtualKeyCode::A, 0x01 => VirtualKeyCode::S, @@ -147,17 +207,18 @@ pub fn to_virtual_keycode(scancode: c_ushort) -> Option { // While F1-F20 have scancodes we can match on, we have to check against UTF-16 // constants for the rest. // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ -pub fn check_function_keys(string: &Option) -> Option { - string - .as_ref() - .and_then(|string| string.encode_utf16().next()) - .and_then(|character| match character { - 0xf718 => Some(VirtualKeyCode::F21), - 0xf719 => Some(VirtualKeyCode::F22), - 0xf71a => Some(VirtualKeyCode::F23), - 0xf71b => Some(VirtualKeyCode::F24), - _ => None, +pub fn check_function_keys(string: &String) -> Option { + if let Some(ch) = string.encode_utf16().next() { + return Some(match ch { + 0xf718 => VirtualKeyCode::F21, + 0xf719 => VirtualKeyCode::F22, + 0xf71a => VirtualKeyCode::F23, + 0xf71b => VirtualKeyCode::F24, + _ => return None, }) + } + + None } pub fn event_mods(event: id) -> ModifiersState { @@ -172,6 +233,16 @@ pub fn event_mods(event: id) -> ModifiersState { } } +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, keymask: NSEventModifierFlags, @@ -184,14 +255,14 @@ pub unsafe fn modifier_event( } else { ElementState::Pressed }; - let keycode = NSEvent::keyCode(ns_event); - let scancode = keycode as u32; - let virtual_keycode = to_virtual_keycode(keycode); + + let scancode = get_scancode(ns_event); + let virtual_keycode = scancode_to_keycode(scancode); Some(WindowEvent::KeyboardInput { device_id: DEVICE_ID, input: KeyboardInput { state, - scancode, + scancode: scancode as _, virtual_keycode, modifiers: event_mods(ns_event), }, diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index cceba6eeeeb..914962c6c6f 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -18,7 +18,7 @@ use { }; use platform_impl::platform::{ app_state::AppState, DEVICE_ID, - event::{check_function_keys, event_mods, modifier_event, to_virtual_keycode}, + event::{check_function_keys, event_mods, modifier_event, char_to_keycode, get_scancode, scancode_to_keycode}, util::{self, IdRef}, ffi::*, window::get_window_id, }; @@ -504,16 +504,53 @@ extern fn do_command_by_selector(this: &Object, _sel: Sel, command: Sel) { trace!("Completed `doCommandBySelector`"); } -fn get_characters(event: id) -> Option { +fn get_characters(event: id, ignore_modifiers: bool) -> String { unsafe { - let characters: id = msg_send![event, characters]; + let characters: id = if ignore_modifiers { + msg_send![event, charactersIgnoringModifiers] + } else { + msg_send![event, characters] + }; + + assert_ne!(characters, nil); let slice = slice::from_raw_parts( characters.UTF8String() as *const c_uchar, characters.len(), ); + let string = str::from_utf8_unchecked(slice); - Some(string.to_owned()) + string.to_owned() + } +} + +// Retrieves a layout-independent keycode given an event. +fn retrieve_keycode(event: id) -> Option { + #[inline] + fn get_code(ev: id, raw: bool) -> Option { + let characters = get_characters(ev, raw); + characters.chars().next().map_or(None, |c| char_to_keycode(c)) } + + // Cmd switches Roman letters for Dvorak-QWERTY layout, so we try modified characters first. + // If we don't get a match, then we fall back to unmodified characters. + let code = get_code(event, false) + .or_else(|| { + get_code(event, true) + }); + + // We've checked all layout related keys, so fall through to scancode. + // Reaching this code means that the key is layout-independent (e.g. Backspace, Return). + // + // We're additionally checking here for F21-F24 keys, since their keycode + // can vary, but we know that they are encoded + // in characters property. + code.or_else(|| { + let scancode = get_scancode(event); + scancode_to_keycode(scancode) + .or_else(|| { + check_function_keys(&get_characters(event, true)) + }) + }) } extern fn key_down(this: &Object, _sel: Sel, event: id) { @@ -522,17 +559,13 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { let state_ptr: *mut c_void = *this.get_ivar("winitState"); let state = &mut *(state_ptr as *mut ViewState); let window_id = WindowId(get_window_id(state.nswindow)); + let characters = get_characters(event, false); - state.raw_characters = get_characters(event); + state.raw_characters = Some(characters.clone()); + + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - // We are checking here for F21-F24 keys, since their keycode - // can vary, but we know that they are encoded - // in characters property. - let virtual_keycode = to_virtual_keycode(keycode).or_else(|| { - check_function_keys(&state.raw_characters) - }); - let scancode = keycode as u32; let is_repeat = msg_send![event, isARepeat]; let window_event = Event::WindowEvent { @@ -548,22 +581,11 @@ extern fn key_down(this: &Object, _sel: Sel, event: id) { }, }; - let characters: id = msg_send![event, characters]; - let slice = slice::from_raw_parts( - characters.UTF8String() as *const c_uchar, - characters.len(), - ); - let string = str::from_utf8_unchecked(slice); - - state.raw_characters = { - Some(string.to_owned()) - }; - let pass_along = { AppState::queue_event(window_event); // Emit `ReceivedCharacter` for key repeats if is_repeat && state.is_key_down { - for character in string.chars() { + for character in characters.chars() { AppState::queue_event(Event::WindowEvent { window_id, event: WindowEvent::ReceivedCharacter(character), @@ -594,16 +616,9 @@ extern fn key_up(this: &Object, _sel: Sel, event: id) { state.is_key_down = false; - // We need characters here to check for additional keys such as - // F21-F24. - let characters = get_characters(event); + let scancode = get_scancode(event) as u32; + let virtual_keycode = retrieve_keycode(event); - let keycode: c_ushort = msg_send![event, keyCode]; - let virtual_keycode = to_virtual_keycode(keycode) - .or_else(|| { - check_function_keys(&characters) - }); - let scancode = keycode as u32; let window_event = Event::WindowEvent { window_id: WindowId(get_window_id(state.nswindow)), event: WindowEvent::KeyboardInput { @@ -707,7 +722,7 @@ extern fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { let state = &mut *(state_ptr as *mut ViewState); let scancode = 0x2f; - let virtual_keycode = to_virtual_keycode(scancode); + let virtual_keycode = scancode_to_keycode(scancode); debug_assert_eq!(virtual_keycode, Some(VirtualKeyCode::Period)); let event: id = msg_send![NSApp(), currentEvent]; From cbfda6c57e9740b49d2b496bda43197f611cb48c Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Wed, 24 Apr 2019 23:47:46 -0600 Subject: [PATCH 5/6] Fixes Signed-off-by: Hal Gentz --- CHANGELOG.md | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f57c4d9263..bf53744b65c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,41 +1,4 @@ # Unreleased -- Changes below are considered **breaking**. -- Change all occurrences of `EventsLoop` to `EventLoop`. -- Previously flat API is now exposed through `event`, `event_loop`, `monitor`, and `window` modules. -- `os` module changes: - - Renamed to `platform`. - - All traits now have platform-specific suffixes. - - Exposes new `desktop` module on Windows, Mac, and Linux. -- Changes to event loop types: - - `EventLoopProxy::wakeup` has been removed in favor of `send_event`. - - **Major:** New `run` method drives winit event loop. - - Returns `!` to ensure API behaves identically across all supported platforms. - - This allows `emscripten` implementation to work without lying about the API. - - `ControlFlow`'s variants have been replaced with `Wait`, `WaitUntil(Instant)`, `Poll`, and `Exit`. - - Is read after `EventsCleared` is processed. - - `Wait` waits until new events are available. - - `WaitUntil` waits until either new events are available or the provided time has been reached. - - `Poll` instantly resumes the event loop. - - `Exit` aborts the event loop. - - Takes a closure that implements `'static + FnMut(Event, &EventLoop, &mut ControlFlow)`. - - `&EventLoop` is provided to allow new `Window`s to be created. - - **Major:** `platform::desktop` module exposes `EventLoopExtDesktop` trait with `run_return` method. - - Behaves identically to `run`, but returns control flow to the calling context can take non-`'static` closures. - - `EventLoop`'s `poll_events` and `run_forever` methods have been removed in favor of `run` and `run_return`. -- Changes to events: - - Remove `Event::Awakened` in favor of `Event::UserEvent(T)`. - - Can be sent with `EventLoopProxy::send_event`. - - Rename `WindowEvent::Refresh` to `WindowEvent::RedrawRequested`. - - `RedrawRequested` can be sent by the user with the `Window::request_redraw` method. - - `EventLoop`, `EventLoopProxy`, and `Event` are now generic over `T`, for use in `UserEvent`. - - **Major:** Add `NewEvents(StartCause)`, `EventsCleared`, and `LoopDestroyed` variants to `Event`. - - `NewEvents` is emitted when new events are ready to be processed by event loop. - - `StartCause` describes why new events are available, with `ResumeTimeReached`, `Poll`, `WaitCancelled`, and `Init` (sent once at start of loop). - - `EventsCleared` is emitted when all available events have been processed. - - Can be used to perform logic that depends on all events being processed (e.g. an iteration of a game loop). - - `LoopDestroyed` is emitted when the `run` or `run_return` method is about to exit. -- Rename `MonitorId` to `MonitorHandle`. -- Removed `serde` implementations from `ControlFlow`. - Changes below are considered **breaking**. - Change all occurrences of `EventsLoop` to `EventLoop`. From a6551f4607ea0bc26df8716dee8115371ef367db Mon Sep 17 00:00:00 2001 From: Hal Gentz Date: Thu, 25 Apr 2019 07:40:56 -0600 Subject: [PATCH 6/6] Fix example Signed-off-by: Hal Gentz --- examples/fullscreen.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 788a608c853..756768abad3 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -70,12 +70,11 @@ fn main() { #[cfg(target_os = "macos")] { if macos_use_simple_fullscreen { - use winit::os::macos::WindowExt; - if WindowExt::set_simple_fullscreen(&window, !is_fullscreen) { + use winit::platform::macos::WindowExtMacOS; + if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) { is_fullscreen = !is_fullscreen; } - - return ControlFlow::Continue; + return; } }