Trying to clena the mess opened by plugins
This commit is contained in:
357
plugins/baseview/src/macos/keyboard.rs
Normal file
357
plugins/baseview/src/macos/keyboard.rs
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2020 The Druid Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Baseview modifications to druid code:
|
||||
// - move from_nsstring function to this file
|
||||
// - update imports, paths etc
|
||||
|
||||
//! Conversion of platform keyboard event into cross-platform event.
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use cocoa::appkit::{NSEvent, NSEventModifierFlags, NSEventType};
|
||||
use cocoa::base::id;
|
||||
use cocoa::foundation::NSString;
|
||||
use keyboard_types::{Code, Key, KeyState, KeyboardEvent, Modifiers};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
use crate::keyboard::code_to_location;
|
||||
|
||||
pub(crate) fn from_nsstring(s: id) -> String {
|
||||
unsafe {
|
||||
let slice = std::slice::from_raw_parts(s.UTF8String() as *const _, s.len());
|
||||
let result = std::str::from_utf8_unchecked(slice);
|
||||
result.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// State for processing of keyboard events.
|
||||
///
|
||||
/// This needs to be stateful for proper processing of dead keys. The current
|
||||
/// implementation is somewhat primitive and is not based on IME; in the future
|
||||
/// when IME is implemented, it will need to be redone somewhat, letting the IME
|
||||
/// be the authoritative source of truth for Unicode string values of keys.
|
||||
///
|
||||
/// Most of the logic in this module is adapted from Mozilla, and in particular
|
||||
/// TextInputHandler.mm.
|
||||
pub(crate) struct KeyboardState {
|
||||
last_mods: Cell<NSEventModifierFlags>,
|
||||
}
|
||||
|
||||
/// Convert a macOS platform key code (keyCode field of NSEvent).
|
||||
///
|
||||
/// The primary source for this mapping is:
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code/code_values
|
||||
///
|
||||
/// It should also match up with CODE_MAP_MAC bindings in
|
||||
/// NativeKeyToDOMCodeName.h.
|
||||
fn key_code_to_code(key_code: u16) -> Code {
|
||||
match key_code {
|
||||
0x00 => Code::KeyA,
|
||||
0x01 => Code::KeyS,
|
||||
0x02 => Code::KeyD,
|
||||
0x03 => Code::KeyF,
|
||||
0x04 => Code::KeyH,
|
||||
0x05 => Code::KeyG,
|
||||
0x06 => Code::KeyZ,
|
||||
0x07 => Code::KeyX,
|
||||
0x08 => Code::KeyC,
|
||||
0x09 => Code::KeyV,
|
||||
0x0a => Code::IntlBackslash,
|
||||
0x0b => Code::KeyB,
|
||||
0x0c => Code::KeyQ,
|
||||
0x0d => Code::KeyW,
|
||||
0x0e => Code::KeyE,
|
||||
0x0f => Code::KeyR,
|
||||
0x10 => Code::KeyY,
|
||||
0x11 => Code::KeyT,
|
||||
0x12 => Code::Digit1,
|
||||
0x13 => Code::Digit2,
|
||||
0x14 => Code::Digit3,
|
||||
0x15 => Code::Digit4,
|
||||
0x16 => Code::Digit6,
|
||||
0x17 => Code::Digit5,
|
||||
0x18 => Code::Equal,
|
||||
0x19 => Code::Digit9,
|
||||
0x1a => Code::Digit7,
|
||||
0x1b => Code::Minus,
|
||||
0x1c => Code::Digit8,
|
||||
0x1d => Code::Digit0,
|
||||
0x1e => Code::BracketRight,
|
||||
0x1f => Code::KeyO,
|
||||
0x20 => Code::KeyU,
|
||||
0x21 => Code::BracketLeft,
|
||||
0x22 => Code::KeyI,
|
||||
0x23 => Code::KeyP,
|
||||
0x24 => Code::Enter,
|
||||
0x25 => Code::KeyL,
|
||||
0x26 => Code::KeyJ,
|
||||
0x27 => Code::Quote,
|
||||
0x28 => Code::KeyK,
|
||||
0x29 => Code::Semicolon,
|
||||
0x2a => Code::Backslash,
|
||||
0x2b => Code::Comma,
|
||||
0x2c => Code::Slash,
|
||||
0x2d => Code::KeyN,
|
||||
0x2e => Code::KeyM,
|
||||
0x2f => Code::Period,
|
||||
0x30 => Code::Tab,
|
||||
0x31 => Code::Space,
|
||||
0x32 => Code::Backquote,
|
||||
0x33 => Code::Backspace,
|
||||
0x34 => Code::NumpadEnter,
|
||||
0x35 => Code::Escape,
|
||||
0x36 => Code::MetaRight,
|
||||
0x37 => Code::MetaLeft,
|
||||
0x38 => Code::ShiftLeft,
|
||||
0x39 => Code::CapsLock,
|
||||
// Note: in the linked source doc, this is "OSLeft"
|
||||
0x3a => Code::AltLeft,
|
||||
0x3b => Code::ControlLeft,
|
||||
0x3c => Code::ShiftRight,
|
||||
// Note: in the linked source doc, this is "OSRight"
|
||||
0x3d => Code::AltRight,
|
||||
0x3e => Code::ControlRight,
|
||||
0x3f => Code::Fn, // No events fired
|
||||
//0x40 => Code::F17,
|
||||
0x41 => Code::NumpadDecimal,
|
||||
0x43 => Code::NumpadMultiply,
|
||||
0x45 => Code::NumpadAdd,
|
||||
0x47 => Code::NumLock,
|
||||
0x48 => Code::AudioVolumeUp,
|
||||
0x49 => Code::AudioVolumeDown,
|
||||
0x4a => Code::AudioVolumeMute,
|
||||
0x4b => Code::NumpadDivide,
|
||||
0x4c => Code::NumpadEnter,
|
||||
0x4e => Code::NumpadSubtract,
|
||||
//0x4f => Code::F18,
|
||||
//0x50 => Code::F19,
|
||||
0x51 => Code::NumpadEqual,
|
||||
0x52 => Code::Numpad0,
|
||||
0x53 => Code::Numpad1,
|
||||
0x54 => Code::Numpad2,
|
||||
0x55 => Code::Numpad3,
|
||||
0x56 => Code::Numpad4,
|
||||
0x57 => Code::Numpad5,
|
||||
0x58 => Code::Numpad6,
|
||||
0x59 => Code::Numpad7,
|
||||
//0x5a => Code::F20,
|
||||
0x5b => Code::Numpad8,
|
||||
0x5c => Code::Numpad9,
|
||||
0x5d => Code::IntlYen,
|
||||
0x5e => Code::IntlRo,
|
||||
0x5f => Code::NumpadComma,
|
||||
0x60 => Code::F5,
|
||||
0x61 => Code::F6,
|
||||
0x62 => Code::F7,
|
||||
0x63 => Code::F3,
|
||||
0x64 => Code::F8,
|
||||
0x65 => Code::F9,
|
||||
0x66 => Code::Lang2,
|
||||
0x67 => Code::F11,
|
||||
0x68 => Code::Lang1,
|
||||
// Note: this is listed as F13, but in testing with a standard
|
||||
// USB kb, this the code produced by PrtSc.
|
||||
0x69 => Code::PrintScreen,
|
||||
//0x6a => Code::F16,
|
||||
//0x6b => Code::F14,
|
||||
0x6d => Code::F10,
|
||||
0x6e => Code::ContextMenu,
|
||||
0x6f => Code::F12,
|
||||
//0x71 => Code::F15,
|
||||
0x72 => Code::Help,
|
||||
0x73 => Code::Home,
|
||||
0x74 => Code::PageUp,
|
||||
0x75 => Code::Delete,
|
||||
0x76 => Code::F4,
|
||||
0x77 => Code::End,
|
||||
0x78 => Code::F2,
|
||||
0x79 => Code::PageDown,
|
||||
0x7a => Code::F1,
|
||||
0x7b => Code::ArrowLeft,
|
||||
0x7c => Code::ArrowRight,
|
||||
0x7d => Code::ArrowDown,
|
||||
0x7e => Code::ArrowUp,
|
||||
_ => Code::Unidentified,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert code to key.
|
||||
///
|
||||
/// On macOS, for non-printable keys, the keyCode we get from the event serves is
|
||||
/// really more of a key than a physical scan code.
|
||||
///
|
||||
/// When this function returns None, the code can be considered printable.
|
||||
///
|
||||
/// The logic for this function is derived from KEY_MAP_COCOA bindings in
|
||||
/// NativeKeyToDOMKeyName.h.
|
||||
fn code_to_key(code: Code) -> Option<Key> {
|
||||
Some(match code {
|
||||
Code::Escape => Key::Escape,
|
||||
Code::ShiftLeft | Code::ShiftRight => Key::Shift,
|
||||
Code::AltLeft | Code::AltRight => Key::Alt,
|
||||
Code::MetaLeft | Code::MetaRight => Key::Meta,
|
||||
Code::ControlLeft | Code::ControlRight => Key::Control,
|
||||
Code::CapsLock => Key::CapsLock,
|
||||
// kVK_ANSI_KeypadClear
|
||||
Code::NumLock => Key::Clear,
|
||||
Code::Fn => Key::Fn,
|
||||
Code::F1 => Key::F1,
|
||||
Code::F2 => Key::F2,
|
||||
Code::F3 => Key::F3,
|
||||
Code::F4 => Key::F4,
|
||||
Code::F5 => Key::F5,
|
||||
Code::F6 => Key::F6,
|
||||
Code::F7 => Key::F7,
|
||||
Code::F8 => Key::F8,
|
||||
Code::F9 => Key::F9,
|
||||
Code::F10 => Key::F10,
|
||||
Code::F11 => Key::F11,
|
||||
Code::F12 => Key::F12,
|
||||
Code::Pause => Key::Pause,
|
||||
Code::ScrollLock => Key::ScrollLock,
|
||||
Code::PrintScreen => Key::PrintScreen,
|
||||
Code::Insert => Key::Insert,
|
||||
Code::Delete => Key::Delete,
|
||||
Code::Tab => Key::Tab,
|
||||
Code::Backspace => Key::Backspace,
|
||||
Code::ContextMenu => Key::ContextMenu,
|
||||
// kVK_JIS_Kana
|
||||
Code::Lang1 => Key::KanjiMode,
|
||||
// kVK_JIS_Eisu
|
||||
Code::Lang2 => Key::Eisu,
|
||||
Code::Home => Key::Home,
|
||||
Code::End => Key::End,
|
||||
Code::PageUp => Key::PageUp,
|
||||
Code::PageDown => Key::PageDown,
|
||||
Code::ArrowLeft => Key::ArrowLeft,
|
||||
Code::ArrowRight => Key::ArrowRight,
|
||||
Code::ArrowUp => Key::ArrowUp,
|
||||
Code::ArrowDown => Key::ArrowDown,
|
||||
Code::Enter => Key::Enter,
|
||||
Code::NumpadEnter => Key::Enter,
|
||||
Code::Help => Key::Help,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_valid_key(s: &str) -> bool {
|
||||
match s.chars().next() {
|
||||
None => false,
|
||||
Some(c) => c >= ' ' && c != '\x7f' && !('\u{e000}'..'\u{f900}').contains(&c),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_modifier_code(code: Code) -> bool {
|
||||
matches!(
|
||||
code,
|
||||
Code::ShiftLeft
|
||||
| Code::ShiftRight
|
||||
| Code::AltLeft
|
||||
| Code::AltRight
|
||||
| Code::ControlLeft
|
||||
| Code::ControlRight
|
||||
| Code::MetaLeft
|
||||
| Code::MetaRight
|
||||
| Code::CapsLock
|
||||
| Code::Help
|
||||
)
|
||||
}
|
||||
|
||||
impl KeyboardState {
|
||||
pub(crate) fn new() -> KeyboardState {
|
||||
let last_mods = Cell::new(NSEventModifierFlags::empty());
|
||||
KeyboardState { last_mods }
|
||||
}
|
||||
|
||||
pub(crate) fn last_mods(&self) -> NSEventModifierFlags {
|
||||
self.last_mods.get()
|
||||
}
|
||||
|
||||
pub(crate) fn process_native_event(&self, event: id) -> Option<KeyboardEvent> {
|
||||
unsafe {
|
||||
let event_type = event.eventType();
|
||||
let key_code = event.keyCode();
|
||||
let code = key_code_to_code(key_code);
|
||||
let location = code_to_location(code);
|
||||
let raw_mods = event.modifierFlags();
|
||||
let modifiers = make_modifiers(raw_mods);
|
||||
let state = match event_type {
|
||||
NSEventType::NSKeyDown => KeyState::Down,
|
||||
NSEventType::NSKeyUp => KeyState::Up,
|
||||
NSEventType::NSFlagsChanged => {
|
||||
// We use `bits` here because we want to distinguish the
|
||||
// device dependent bits (when both left and right keys
|
||||
// may be pressed, for example).
|
||||
let any_down = raw_mods.bits() & !self.last_mods.get().bits();
|
||||
self.last_mods.set(raw_mods);
|
||||
if is_modifier_code(code) {
|
||||
if any_down == 0 {
|
||||
KeyState::Up
|
||||
} else {
|
||||
KeyState::Down
|
||||
}
|
||||
} else {
|
||||
// HandleFlagsChanged has some logic for this; it might
|
||||
// happen when an app is deactivated by Command-Tab. In
|
||||
// that case, the best thing to do is synthesize the event
|
||||
// from the modifiers. But a challenge there is that we
|
||||
// might get multiple events.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let is_composing = false;
|
||||
let repeat: bool = event_type == NSEventType::NSKeyDown && msg_send![event, isARepeat];
|
||||
let key = if let Some(key) = code_to_key(code) {
|
||||
key
|
||||
} else {
|
||||
let characters = from_nsstring(event.characters());
|
||||
if is_valid_key(&characters) {
|
||||
Key::Character(characters)
|
||||
} else {
|
||||
let chars_ignoring = from_nsstring(event.charactersIgnoringModifiers());
|
||||
if is_valid_key(&chars_ignoring) {
|
||||
Key::Character(chars_ignoring)
|
||||
} else {
|
||||
// There may be more heroic things we can do here.
|
||||
Key::Unidentified
|
||||
}
|
||||
}
|
||||
};
|
||||
let event =
|
||||
KeyboardEvent { code, key, location, modifiers, state, is_composing, repeat };
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MODIFIER_MAP: &[(NSEventModifierFlags, Modifiers)] = &[
|
||||
(NSEventModifierFlags::NSShiftKeyMask, Modifiers::SHIFT),
|
||||
(NSEventModifierFlags::NSAlternateKeyMask, Modifiers::ALT),
|
||||
(NSEventModifierFlags::NSControlKeyMask, Modifiers::CONTROL),
|
||||
(NSEventModifierFlags::NSCommandKeyMask, Modifiers::META),
|
||||
(NSEventModifierFlags::NSAlphaShiftKeyMask, Modifiers::CAPS_LOCK),
|
||||
];
|
||||
|
||||
pub(crate) fn make_modifiers(raw: NSEventModifierFlags) -> Modifiers {
|
||||
let mut modifiers = Modifiers::empty();
|
||||
for &(flags, mods) in MODIFIER_MAP {
|
||||
if raw.contains(flags) {
|
||||
modifiers |= mods;
|
||||
}
|
||||
}
|
||||
modifiers
|
||||
}
|
||||
21
plugins/baseview/src/macos/mod.rs
Normal file
21
plugins/baseview/src/macos/mod.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
// This is required because the objc crate is causing a lot of warnings: https://github.com/SSheldon/rust-objc/issues/125
|
||||
// Eventually we should migrate to the objc2 crate and remove this.
|
||||
#![allow(unexpected_cfgs)]
|
||||
|
||||
mod keyboard;
|
||||
mod view;
|
||||
mod window;
|
||||
|
||||
pub use window::*;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
mod consts {
|
||||
use cocoa::foundation::NSUInteger;
|
||||
|
||||
pub const NSDragOperationNone: NSUInteger = 0;
|
||||
pub const NSDragOperationCopy: NSUInteger = 1;
|
||||
pub const NSDragOperationLink: NSUInteger = 2;
|
||||
pub const NSDragOperationGeneric: NSUInteger = 4;
|
||||
pub const NSDragOperationMove: NSUInteger = 16;
|
||||
}
|
||||
use consts::*;
|
||||
617
plugins/baseview/src/macos/view.rs
Normal file
617
plugins/baseview/src/macos/view.rs
Normal file
@@ -0,0 +1,617 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use cocoa::appkit::{NSEvent, NSFilenamesPboardType, NSView, NSWindow};
|
||||
use cocoa::base::{id, nil, BOOL, NO, YES};
|
||||
use cocoa::foundation::{NSArray, NSPoint, NSRect, NSSize, NSUInteger};
|
||||
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::MouseEvent::{ButtonPressed, ButtonReleased};
|
||||
use crate::{
|
||||
DropData, DropEffect, Event, EventStatus, MouseButton, MouseEvent, Point, ScrollDelta, Size,
|
||||
WindowEvent, WindowInfo, WindowOpenOptions,
|
||||
};
|
||||
|
||||
use super::keyboard::{from_nsstring, make_modifiers};
|
||||
use super::window::WindowState;
|
||||
use super::{
|
||||
NSDragOperationCopy, NSDragOperationGeneric, NSDragOperationLink, NSDragOperationMove,
|
||||
NSDragOperationNone,
|
||||
};
|
||||
|
||||
/// Name of the field used to store the `WindowState` pointer.
|
||||
pub(super) const BASEVIEW_STATE_IVAR: &str = "baseview_state";
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {
|
||||
static NSWindowDidBecomeKeyNotification: id;
|
||||
static NSWindowDidResignKeyNotification: id;
|
||||
}
|
||||
|
||||
macro_rules! add_simple_mouse_class_method {
|
||||
($class:ident, $sel:ident, $event:expr) => {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $sel(this: &Object, _: Sel, _: id){
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
state.trigger_event(Event::Mouse($event));
|
||||
}
|
||||
|
||||
$class.add_method(
|
||||
sel!($sel:),
|
||||
$sel as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/// Similar to [add_simple_mouse_class_method!], but this creates its own event object for the
|
||||
/// press/release event and adds the active modifier keys to that event.
|
||||
macro_rules! add_mouse_button_class_method {
|
||||
($class:ident, $sel:ident, $event_ty:ident, $button:expr) => {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $sel(this: &Object, _: Sel, event: id){
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
let modifiers = unsafe { NSEvent::modifierFlags(event) };
|
||||
|
||||
state.trigger_event(Event::Mouse($event_ty {
|
||||
button: $button,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
}));
|
||||
}
|
||||
|
||||
$class.add_method(
|
||||
sel!($sel:),
|
||||
$sel as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! add_simple_keyboard_class_method {
|
||||
($class:ident, $sel:ident) => {
|
||||
#[allow(non_snake_case)]
|
||||
extern "C" fn $sel(this: &Object, _: Sel, event: id){
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
if let Some(key_event) = state.process_native_key_event(event){
|
||||
let status = state.trigger_event(Event::Keyboard(key_event));
|
||||
|
||||
if let EventStatus::Ignored = status {
|
||||
unsafe {
|
||||
let superclass = msg_send![this, superclass];
|
||||
|
||||
let () = msg_send![super(this, superclass), $sel:event];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class.add_method(
|
||||
sel!($sel:),
|
||||
$sel as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn register_notification(observer: id, notification_name: id, object: id) {
|
||||
let notification_center: id = msg_send![class!(NSNotificationCenter), defaultCenter];
|
||||
|
||||
let _: () = msg_send![
|
||||
notification_center,
|
||||
addObserver:observer
|
||||
selector:sel!(handleNotification:)
|
||||
name:notification_name
|
||||
object:object
|
||||
];
|
||||
}
|
||||
|
||||
pub(super) unsafe fn create_view(window_options: &WindowOpenOptions) -> id {
|
||||
let class = create_view_class();
|
||||
|
||||
let view: id = msg_send![class, alloc];
|
||||
|
||||
let size = window_options.size;
|
||||
|
||||
view.initWithFrame_(NSRect::new(NSPoint::new(0., 0.), NSSize::new(size.width, size.height)));
|
||||
|
||||
register_notification(view, NSWindowDidBecomeKeyNotification, nil);
|
||||
register_notification(view, NSWindowDidResignKeyNotification, nil);
|
||||
|
||||
let _: id = msg_send![
|
||||
view,
|
||||
registerForDraggedTypes: NSArray::arrayWithObjects(nil, &[NSFilenamesPboardType])
|
||||
];
|
||||
|
||||
view
|
||||
}
|
||||
|
||||
unsafe fn create_view_class() -> &'static Class {
|
||||
// Use unique class names so that there are no conflicts between different
|
||||
// instances. The class is deleted when the view is released. Previously,
|
||||
// the class was stored in a OnceCell after creation. This way, we didn't
|
||||
// have to recreate it each time a view was opened, but now we don't leave
|
||||
// any class definitions lying around when the plugin is closed.
|
||||
let class_name = format!("BaseviewNSView_{}", Uuid::new_v4().to_simple());
|
||||
let mut class = ClassDecl::new(&class_name, class!(NSView)).unwrap();
|
||||
|
||||
class.add_method(
|
||||
sel!(acceptsFirstResponder),
|
||||
property_yes as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(becomeFirstResponder),
|
||||
become_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(resignFirstResponder),
|
||||
resign_first_responder as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
class.add_method(sel!(isFlipped), property_yes as extern "C" fn(&Object, Sel) -> BOOL);
|
||||
class.add_method(
|
||||
sel!(preservesContentInLiveResize),
|
||||
property_no as extern "C" fn(&Object, Sel) -> BOOL,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(acceptsFirstMouse:),
|
||||
accepts_first_mouse as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
);
|
||||
|
||||
class.add_method(
|
||||
sel!(windowShouldClose:),
|
||||
window_should_close as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
);
|
||||
class.add_method(sel!(dealloc), dealloc as extern "C" fn(&mut Object, Sel));
|
||||
class.add_method(
|
||||
sel!(viewWillMoveToWindow:),
|
||||
view_will_move_to_window as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
class.add_method(
|
||||
sel!(updateTrackingAreas:),
|
||||
update_tracking_areas as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
class.add_method(sel!(mouseMoved:), mouse_moved as extern "C" fn(&Object, Sel, id));
|
||||
class.add_method(sel!(mouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id));
|
||||
class.add_method(sel!(rightMouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id));
|
||||
class.add_method(sel!(otherMouseDragged:), mouse_moved as extern "C" fn(&Object, Sel, id));
|
||||
|
||||
class.add_method(sel!(scrollWheel:), scroll_wheel as extern "C" fn(&Object, Sel, id));
|
||||
|
||||
class.add_method(
|
||||
sel!(viewDidChangeBackingProperties:),
|
||||
view_did_change_backing_properties as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
class.add_method(
|
||||
sel!(setFrameSize:),
|
||||
set_frame_size as extern "C" fn(&Object, Sel, NSSize),
|
||||
);
|
||||
|
||||
class.add_method(
|
||||
sel!(draggingEntered:),
|
||||
dragging_entered as extern "C" fn(&Object, Sel, id) -> NSUInteger,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(prepareForDragOperation:),
|
||||
prepare_for_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(performDragOperation:),
|
||||
perform_drag_operation as extern "C" fn(&Object, Sel, id) -> BOOL,
|
||||
);
|
||||
class.add_method(
|
||||
sel!(draggingUpdated:),
|
||||
dragging_updated as extern "C" fn(&Object, Sel, id) -> NSUInteger,
|
||||
);
|
||||
class.add_method(sel!(draggingExited:), dragging_exited as extern "C" fn(&Object, Sel, id));
|
||||
class.add_method(
|
||||
sel!(handleNotification:),
|
||||
handle_notification as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
add_mouse_button_class_method!(class, mouseDown, ButtonPressed, MouseButton::Left);
|
||||
add_mouse_button_class_method!(class, mouseUp, ButtonReleased, MouseButton::Left);
|
||||
add_mouse_button_class_method!(class, rightMouseDown, ButtonPressed, MouseButton::Right);
|
||||
add_mouse_button_class_method!(class, rightMouseUp, ButtonReleased, MouseButton::Right);
|
||||
add_mouse_button_class_method!(class, otherMouseDown, ButtonPressed, MouseButton::Middle);
|
||||
add_mouse_button_class_method!(class, otherMouseUp, ButtonReleased, MouseButton::Middle);
|
||||
add_simple_mouse_class_method!(class, mouseEntered, MouseEvent::CursorEntered);
|
||||
add_simple_mouse_class_method!(class, mouseExited, MouseEvent::CursorLeft);
|
||||
|
||||
add_simple_keyboard_class_method!(class, keyDown);
|
||||
add_simple_keyboard_class_method!(class, keyUp);
|
||||
add_simple_keyboard_class_method!(class, flagsChanged);
|
||||
|
||||
class.add_ivar::<*mut c_void>(BASEVIEW_STATE_IVAR);
|
||||
|
||||
class.register()
|
||||
}
|
||||
|
||||
extern "C" fn property_yes(_this: &Object, _sel: Sel) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn property_no(_this: &Object, _sel: Sel) -> BOOL {
|
||||
NO
|
||||
}
|
||||
|
||||
extern "C" fn accepts_first_mouse(_this: &Object, _sel: Sel, _event: id) -> BOOL {
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn become_first_responder(this: &Object, _sel: Sel) -> BOOL {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
let is_key_window = unsafe {
|
||||
let window: id = msg_send![this, window];
|
||||
if window != nil {
|
||||
let is_key_window: BOOL = msg_send![window, isKeyWindow];
|
||||
is_key_window == YES
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
if is_key_window {
|
||||
state.trigger_deferrable_event(Event::Window(WindowEvent::Focused));
|
||||
}
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn resign_first_responder(this: &Object, _sel: Sel) -> BOOL {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
state.trigger_deferrable_event(Event::Window(WindowEvent::Unfocused));
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn window_should_close(this: &Object, _: Sel, _sender: id) -> BOOL {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
state.trigger_event(Event::Window(WindowEvent::WillClose));
|
||||
|
||||
state.window_inner.close();
|
||||
|
||||
NO
|
||||
}
|
||||
|
||||
extern "C" fn dealloc(this: &mut Object, _sel: Sel) {
|
||||
unsafe {
|
||||
let class = msg_send![this, class];
|
||||
|
||||
let superclass = msg_send![this, superclass];
|
||||
let () = msg_send![super(this, superclass), dealloc];
|
||||
|
||||
// Delete class
|
||||
::objc::runtime::objc_disposeClassPair(class);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn view_did_change_backing_properties(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let ns_window: *mut Object = msg_send![this, window];
|
||||
|
||||
let scale_factor: f64 =
|
||||
if ns_window.is_null() { 1.0 } else { NSWindow::backingScaleFactor(ns_window) };
|
||||
|
||||
let state = WindowState::from_view(this);
|
||||
|
||||
let bounds: NSRect = msg_send![this, bounds];
|
||||
|
||||
let new_window_info = WindowInfo::from_logical_size(
|
||||
Size::new(bounds.size.width, bounds.size.height),
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
let window_info = state.window_info.get();
|
||||
|
||||
// Only send the event when the window's size has actually changed to be in line with the
|
||||
// other platform implementations
|
||||
if new_window_info.physical_size() != window_info.physical_size() {
|
||||
state.window_info.set(new_window_info);
|
||||
state.trigger_event(Event::Window(WindowEvent::Resized(new_window_info)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Init/reinit tracking area
|
||||
///
|
||||
/// Info:
|
||||
/// https://developer.apple.com/documentation/appkit/nstrackingarea
|
||||
/// https://developer.apple.com/documentation/appkit/nstrackingarea/options
|
||||
/// https://developer.apple.com/documentation/appkit/nstrackingareaoptions
|
||||
unsafe fn reinit_tracking_area(this: &Object, tracking_area: *mut Object) {
|
||||
let options: usize = {
|
||||
let mouse_entered_and_exited = 0x01;
|
||||
let tracking_mouse_moved = 0x02;
|
||||
let tracking_cursor_update = 0x04;
|
||||
let tracking_active_in_active_app = 0x40;
|
||||
let tracking_in_visible_rect = 0x200;
|
||||
let tracking_enabled_during_mouse_drag = 0x400;
|
||||
|
||||
mouse_entered_and_exited
|
||||
| tracking_mouse_moved
|
||||
| tracking_cursor_update
|
||||
| tracking_active_in_active_app
|
||||
| tracking_in_visible_rect
|
||||
| tracking_enabled_during_mouse_drag
|
||||
};
|
||||
|
||||
let bounds: NSRect = msg_send![this, bounds];
|
||||
|
||||
*tracking_area = msg_send![tracking_area,
|
||||
initWithRect:bounds
|
||||
options:options
|
||||
owner:this
|
||||
userInfo:nil
|
||||
];
|
||||
}
|
||||
|
||||
extern "C" fn view_will_move_to_window(this: &Object, _self: Sel, new_window: id) {
|
||||
unsafe {
|
||||
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
|
||||
let tracking_area_count = NSArray::count(tracking_areas);
|
||||
|
||||
if new_window == nil {
|
||||
if tracking_area_count != 0 {
|
||||
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
|
||||
|
||||
let _: () = msg_send![this, removeTrackingArea: tracking_area];
|
||||
let _: () = msg_send![tracking_area, release];
|
||||
}
|
||||
} else {
|
||||
if tracking_area_count == 0 {
|
||||
let class = Class::get("NSTrackingArea").unwrap();
|
||||
|
||||
let tracking_area: *mut Object = msg_send![class, alloc];
|
||||
|
||||
reinit_tracking_area(this, tracking_area);
|
||||
|
||||
let _: () = msg_send![this, addTrackingArea: tracking_area];
|
||||
}
|
||||
|
||||
let _: () = msg_send![new_window, setAcceptsMouseMovedEvents: YES];
|
||||
let _: () = msg_send![new_window, makeFirstResponder: this];
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let superclass = msg_send![this, superclass];
|
||||
|
||||
let () = msg_send![super(this, superclass), viewWillMoveToWindow: new_window];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn update_tracking_areas(this: &Object, _self: Sel, _: id) {
|
||||
unsafe {
|
||||
let tracking_areas: *mut Object = msg_send![this, trackingAreas];
|
||||
let tracking_area = NSArray::objectAtIndex(tracking_areas, 0);
|
||||
|
||||
reinit_tracking_area(this, tracking_area);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn set_frame_size(this: &Object, _: Sel, new_size: NSSize) {
|
||||
unsafe {
|
||||
let superclass = msg_send![this, superclass];
|
||||
let () = msg_send![super(this, superclass), setFrameSize: new_size];
|
||||
}
|
||||
|
||||
let state_ptr: *const c_void = unsafe { *this.get_ivar(BASEVIEW_STATE_IVAR) };
|
||||
if state_ptr.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
let scale_factor = unsafe {
|
||||
let ns_window: *mut Object = msg_send![this, window];
|
||||
if ns_window.is_null() { 1.0 } else { NSWindow::backingScaleFactor(ns_window) }
|
||||
};
|
||||
|
||||
let new_window_info = WindowInfo::from_logical_size(
|
||||
Size::new(new_size.width, new_size.height),
|
||||
scale_factor,
|
||||
);
|
||||
|
||||
let old_info = state.window_info.get();
|
||||
if new_window_info.physical_size() != old_info.physical_size() {
|
||||
state.window_info.set(new_window_info);
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
if let Some(gl_context) = &state.window_inner.gl_context {
|
||||
gl_context.resize(new_size);
|
||||
}
|
||||
|
||||
state.trigger_deferrable_event(Event::Window(WindowEvent::Resized(new_window_info)));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_screen_position() -> Point {
|
||||
unsafe {
|
||||
let screen_point: NSPoint = msg_send![class!(NSEvent), mouseLocation];
|
||||
let main_screen: id = msg_send![class!(NSScreen), mainScreen];
|
||||
let screen_frame: NSRect = msg_send![main_screen, frame];
|
||||
Point {
|
||||
x: screen_point.x,
|
||||
y: screen_frame.size.height - screen_point.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn mouse_moved(this: &Object, _sel: Sel, event: id) {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
let point: NSPoint = unsafe {
|
||||
let point = NSEvent::locationInWindow(event);
|
||||
|
||||
msg_send![this, convertPoint:point fromView:nil]
|
||||
};
|
||||
let modifiers = unsafe { NSEvent::modifierFlags(event) };
|
||||
|
||||
let position = Point { x: point.x, y: point.y };
|
||||
let screen_position = get_screen_position();
|
||||
|
||||
state.trigger_event(Event::Mouse(MouseEvent::CursorMoved {
|
||||
position,
|
||||
screen_position,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
}));
|
||||
}
|
||||
|
||||
extern "C" fn scroll_wheel(this: &Object, _: Sel, event: id) {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
let delta = unsafe {
|
||||
let x = NSEvent::scrollingDeltaX(event) as f32;
|
||||
let y = NSEvent::scrollingDeltaY(event) as f32;
|
||||
|
||||
if NSEvent::hasPreciseScrollingDeltas(event) != NO {
|
||||
ScrollDelta::Pixels { x, y }
|
||||
} else {
|
||||
ScrollDelta::Lines { x, y }
|
||||
}
|
||||
};
|
||||
|
||||
let modifiers = unsafe { NSEvent::modifierFlags(event) };
|
||||
|
||||
state.trigger_event(Event::Mouse(MouseEvent::WheelScrolled {
|
||||
delta,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
}));
|
||||
}
|
||||
|
||||
fn get_drag_position(sender: id) -> (Point, Point) {
|
||||
let point: NSPoint = unsafe { msg_send![sender, draggingLocation] };
|
||||
(Point::new(point.x, point.y), get_screen_position())
|
||||
}
|
||||
|
||||
fn get_drop_data(sender: id) -> DropData {
|
||||
if sender == nil {
|
||||
return DropData::None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let pasteboard: id = msg_send![sender, draggingPasteboard];
|
||||
let file_list: id = msg_send![pasteboard, propertyListForType: NSFilenamesPboardType];
|
||||
|
||||
if file_list == nil {
|
||||
return DropData::None;
|
||||
}
|
||||
|
||||
let mut files = vec![];
|
||||
for i in 0..NSArray::count(file_list) {
|
||||
let data = NSArray::objectAtIndex(file_list, i);
|
||||
files.push(from_nsstring(data).into());
|
||||
}
|
||||
|
||||
DropData::Files(files)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_event(window_state: &WindowState, event: MouseEvent) -> NSUInteger {
|
||||
let event_status = window_state.trigger_event(Event::Mouse(event));
|
||||
match event_status {
|
||||
EventStatus::AcceptDrop(DropEffect::Copy) => NSDragOperationCopy,
|
||||
EventStatus::AcceptDrop(DropEffect::Move) => NSDragOperationMove,
|
||||
EventStatus::AcceptDrop(DropEffect::Link) => NSDragOperationLink,
|
||||
EventStatus::AcceptDrop(DropEffect::Scroll) => NSDragOperationGeneric,
|
||||
_ => NSDragOperationNone,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn dragging_entered(this: &Object, _sel: Sel, sender: id) -> NSUInteger {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
let modifiers = state.keyboard_state().last_mods();
|
||||
let drop_data = get_drop_data(sender);
|
||||
let (position, screen_position) = get_drag_position(sender);
|
||||
|
||||
let event = MouseEvent::DragEntered {
|
||||
position,
|
||||
screen_position,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
data: drop_data,
|
||||
};
|
||||
|
||||
on_event(&state, event)
|
||||
}
|
||||
|
||||
extern "C" fn dragging_updated(this: &Object, _sel: Sel, sender: id) -> NSUInteger {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
let modifiers = state.keyboard_state().last_mods();
|
||||
let drop_data = get_drop_data(sender);
|
||||
let (position, screen_position) = get_drag_position(sender);
|
||||
|
||||
let event = MouseEvent::DragMoved {
|
||||
position,
|
||||
screen_position,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
data: drop_data,
|
||||
};
|
||||
|
||||
on_event(&state, event)
|
||||
}
|
||||
|
||||
extern "C" fn prepare_for_drag_operation(_this: &Object, _sel: Sel, _sender: id) -> BOOL {
|
||||
// Always accept drag operation if we get this far
|
||||
// This function won't be called unless dragging_entered/updated
|
||||
// has returned an acceptable operation
|
||||
YES
|
||||
}
|
||||
|
||||
extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: id) -> BOOL {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
let modifiers = state.keyboard_state().last_mods();
|
||||
let drop_data = get_drop_data(sender);
|
||||
let (position, screen_position) = get_drag_position(sender);
|
||||
|
||||
let event = MouseEvent::DragDropped {
|
||||
position,
|
||||
screen_position,
|
||||
modifiers: make_modifiers(modifiers),
|
||||
data: drop_data,
|
||||
};
|
||||
|
||||
let event_status = state.trigger_event(Event::Mouse(event));
|
||||
match event_status {
|
||||
EventStatus::AcceptDrop(_) => YES,
|
||||
_ => NO,
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn dragging_exited(this: &Object, _sel: Sel, _sender: id) {
|
||||
let state = unsafe { WindowState::from_view(this) };
|
||||
|
||||
on_event(&state, MouseEvent::DragLeft);
|
||||
}
|
||||
|
||||
extern "C" fn handle_notification(this: &Object, _cmd: Sel, notification: id) {
|
||||
unsafe {
|
||||
let state = WindowState::from_view(this);
|
||||
|
||||
// The subject of the notication, in this case an NSWindow object.
|
||||
let notification_object: id = msg_send![notification, object];
|
||||
|
||||
// The NSWindow object associated with our NSView.
|
||||
let window: id = msg_send![this, window];
|
||||
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
|
||||
// Only trigger focus events if the NSWindow that's being notified about is our window,
|
||||
// and if the window's first responder is our NSView.
|
||||
// If the first responder isn't our NSView, the focus events will instead be triggered
|
||||
// by the becomeFirstResponder and resignFirstResponder methods on the NSView itself.
|
||||
if notification_object == window && std::ptr::eq(first_responder, this) {
|
||||
let is_key_window: BOOL = msg_send![window, isKeyWindow];
|
||||
state.trigger_event(Event::Window(if is_key_window == YES {
|
||||
WindowEvent::Focused
|
||||
} else {
|
||||
WindowEvent::Unfocused
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
484
plugins/baseview/src/macos/window.rs
Normal file
484
plugins/baseview/src/macos/window.rs
Normal file
@@ -0,0 +1,484 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::rc::Rc;
|
||||
|
||||
use cocoa::appkit::{
|
||||
NSApp, NSApplication, NSApplicationActivationPolicyRegular, NSBackingStoreBuffered,
|
||||
NSPasteboard, NSView, NSWindow, NSWindowStyleMask,
|
||||
};
|
||||
use cocoa::base::{id, nil, BOOL, NO, YES};
|
||||
use cocoa::foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize, NSString};
|
||||
use core_foundation::runloop::{
|
||||
CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, __CFRunLoopTimer, kCFRunLoopDefaultMode,
|
||||
};
|
||||
use keyboard_types::KeyboardEvent;
|
||||
use objc::class;
|
||||
use objc::{msg_send, runtime::Object, sel, sel_impl};
|
||||
use raw_window_handle::{
|
||||
AppKitDisplayHandle, AppKitWindowHandle, HasRawDisplayHandle, HasRawWindowHandle,
|
||||
RawDisplayHandle, RawWindowHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Event, EventStatus, MouseCursor, Size, WindowHandler, WindowInfo, WindowOpenOptions,
|
||||
WindowScalePolicy,
|
||||
};
|
||||
|
||||
use super::keyboard::KeyboardState;
|
||||
use super::view::{create_view, BASEVIEW_STATE_IVAR};
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
use crate::gl::{GlConfig, GlContext};
|
||||
|
||||
pub struct WindowHandle {
|
||||
state: Rc<WindowState>,
|
||||
}
|
||||
|
||||
impl WindowHandle {
|
||||
pub fn close(&mut self) {
|
||||
self.state.window_inner.close();
|
||||
}
|
||||
|
||||
pub fn is_open(&self) -> bool {
|
||||
self.state.window_inner.open.get()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl HasRawWindowHandle for WindowHandle {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
self.state.window_inner.raw_window_handle()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct WindowInner {
|
||||
open: Cell<bool>,
|
||||
|
||||
/// Only set if we created the parent window, i.e. we are running in
|
||||
/// parentless mode
|
||||
ns_app: Cell<Option<id>>,
|
||||
/// Only set if we created the parent window, i.e. we are running in
|
||||
/// parentless mode
|
||||
ns_window: Cell<Option<id>>,
|
||||
/// Our subclassed NSView
|
||||
ns_view: id,
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
pub(super) gl_context: Option<GlContext>,
|
||||
}
|
||||
|
||||
impl WindowInner {
|
||||
pub(super) fn close(&self) {
|
||||
if self.open.get() {
|
||||
self.open.set(false);
|
||||
unsafe {
|
||||
// Take back ownership of the NSView's Rc<WindowState>
|
||||
let state_ptr: *const c_void = *(*self.ns_view).get_ivar(BASEVIEW_STATE_IVAR);
|
||||
let window_state = Rc::from_raw(state_ptr as *mut WindowState);
|
||||
|
||||
// Cancel the frame timer
|
||||
if let Some(frame_timer) = window_state.frame_timer.take() {
|
||||
CFRunLoop::get_current().remove_timer(&frame_timer, kCFRunLoopDefaultMode);
|
||||
}
|
||||
|
||||
// Deregister NSView from NotificationCenter.
|
||||
let notification_center: id =
|
||||
msg_send![class!(NSNotificationCenter), defaultCenter];
|
||||
let () = msg_send![notification_center, removeObserver:self.ns_view];
|
||||
|
||||
drop(window_state);
|
||||
|
||||
// Close the window if in non-parented mode
|
||||
if let Some(ns_window) = self.ns_window.take() {
|
||||
ns_window.close();
|
||||
}
|
||||
|
||||
// Ensure that the NSView is detached from the parent window
|
||||
self.ns_view.removeFromSuperview();
|
||||
let () = msg_send![self.ns_view as id, release];
|
||||
|
||||
// If in non-parented mode, we want to also quit the app altogether
|
||||
let app = self.ns_app.take();
|
||||
if let Some(app) = app {
|
||||
app.stop_(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
if self.open.get() {
|
||||
let ns_window = self.ns_window.get().unwrap_or(ptr::null_mut()) as *mut c_void;
|
||||
|
||||
let mut handle = AppKitWindowHandle::empty();
|
||||
handle.ns_window = ns_window;
|
||||
handle.ns_view = self.ns_view as *mut c_void;
|
||||
|
||||
return RawWindowHandle::AppKit(handle);
|
||||
}
|
||||
|
||||
RawWindowHandle::AppKit(AppKitWindowHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window<'a> {
|
||||
inner: &'a WindowInner,
|
||||
}
|
||||
|
||||
impl<'a> Window<'a> {
|
||||
pub fn open_parented<P, H, B>(parent: &P, options: WindowOpenOptions, build: B) -> WindowHandle
|
||||
where
|
||||
P: HasRawWindowHandle,
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
let pool = unsafe { NSAutoreleasePool::new(nil) };
|
||||
|
||||
let scaling = match options.scale {
|
||||
WindowScalePolicy::ScaleFactor(scale) => scale,
|
||||
WindowScalePolicy::SystemScaleFactor => 1.0,
|
||||
};
|
||||
|
||||
let window_info = WindowInfo::from_logical_size(options.size, scaling);
|
||||
|
||||
let handle = if let RawWindowHandle::AppKit(handle) = parent.raw_window_handle() {
|
||||
handle
|
||||
} else {
|
||||
panic!("Not a macOS window");
|
||||
};
|
||||
|
||||
let ns_view = unsafe { create_view(&options) };
|
||||
|
||||
let window_inner = WindowInner {
|
||||
open: Cell::new(true),
|
||||
ns_app: Cell::new(None),
|
||||
ns_window: Cell::new(None),
|
||||
ns_view,
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
gl_context: options
|
||||
.gl_config
|
||||
.map(|gl_config| Self::create_gl_context(None, ns_view, gl_config)),
|
||||
};
|
||||
|
||||
let window_handle = Self::init(window_inner, window_info, build);
|
||||
|
||||
unsafe {
|
||||
let _: id = msg_send![handle.ns_view as *mut Object, addSubview: ns_view];
|
||||
|
||||
let () = msg_send![pool, drain];
|
||||
}
|
||||
|
||||
window_handle
|
||||
}
|
||||
|
||||
pub fn open_blocking<H, B>(options: WindowOpenOptions, build: B)
|
||||
where
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
let pool = unsafe { NSAutoreleasePool::new(nil) };
|
||||
|
||||
// It seems prudent to run NSApp() here before doing other
|
||||
// work. It runs [NSApplication sharedApplication], which is
|
||||
// what is run at the very start of the Xcode-generated main
|
||||
// function of a cocoa app according to:
|
||||
// https://developer.apple.com/documentation/appkit/nsapplication
|
||||
let app = unsafe { NSApp() };
|
||||
|
||||
unsafe {
|
||||
app.setActivationPolicy_(NSApplicationActivationPolicyRegular);
|
||||
}
|
||||
|
||||
let scaling = match options.scale {
|
||||
WindowScalePolicy::ScaleFactor(scale) => scale,
|
||||
WindowScalePolicy::SystemScaleFactor => 1.0,
|
||||
};
|
||||
|
||||
let window_info = WindowInfo::from_logical_size(options.size, scaling);
|
||||
|
||||
let rect = NSRect::new(
|
||||
NSPoint::new(0.0, 0.0),
|
||||
NSSize::new(window_info.logical_size().width, window_info.logical_size().height),
|
||||
);
|
||||
|
||||
let ns_window = unsafe {
|
||||
let ns_window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_(
|
||||
rect,
|
||||
NSWindowStyleMask::NSTitledWindowMask
|
||||
| NSWindowStyleMask::NSClosableWindowMask
|
||||
| NSWindowStyleMask::NSMiniaturizableWindowMask,
|
||||
NSBackingStoreBuffered,
|
||||
NO,
|
||||
);
|
||||
ns_window.center();
|
||||
|
||||
let title = NSString::alloc(nil).init_str(&options.title).autorelease();
|
||||
ns_window.setTitle_(title);
|
||||
|
||||
ns_window.makeKeyAndOrderFront_(nil);
|
||||
|
||||
ns_window
|
||||
};
|
||||
|
||||
let ns_view = unsafe { create_view(&options) };
|
||||
|
||||
let window_inner = WindowInner {
|
||||
open: Cell::new(true),
|
||||
ns_app: Cell::new(Some(app)),
|
||||
ns_window: Cell::new(Some(ns_window)),
|
||||
ns_view,
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
gl_context: options
|
||||
.gl_config
|
||||
.map(|gl_config| Self::create_gl_context(Some(ns_window), ns_view, gl_config)),
|
||||
};
|
||||
|
||||
let _ = Self::init(window_inner, window_info, build);
|
||||
|
||||
unsafe {
|
||||
ns_window.setContentView_(ns_view);
|
||||
ns_window.setDelegate_(ns_view);
|
||||
|
||||
let () = msg_send![pool, drain];
|
||||
|
||||
app.run();
|
||||
}
|
||||
}
|
||||
|
||||
fn init<H, B>(window_inner: WindowInner, window_info: WindowInfo, build: B) -> WindowHandle
|
||||
where
|
||||
H: WindowHandler + 'static,
|
||||
B: FnOnce(&mut crate::Window) -> H,
|
||||
B: Send + 'static,
|
||||
{
|
||||
let mut window = crate::Window::new(Window { inner: &window_inner });
|
||||
let window_handler = Box::new(build(&mut window));
|
||||
|
||||
let ns_view = window_inner.ns_view;
|
||||
|
||||
let window_state = Rc::new(WindowState {
|
||||
window_inner,
|
||||
window_handler: RefCell::new(window_handler),
|
||||
keyboard_state: KeyboardState::new(),
|
||||
frame_timer: Cell::new(None),
|
||||
window_info: Cell::new(window_info),
|
||||
deferred_events: RefCell::default(),
|
||||
});
|
||||
|
||||
let window_state_ptr = Rc::into_raw(Rc::clone(&window_state));
|
||||
|
||||
unsafe {
|
||||
(*ns_view).set_ivar(BASEVIEW_STATE_IVAR, window_state_ptr as *const c_void);
|
||||
|
||||
WindowState::setup_timer(window_state_ptr);
|
||||
}
|
||||
|
||||
WindowHandle { state: window_state }
|
||||
}
|
||||
|
||||
pub fn close(&mut self) {
|
||||
self.inner.close();
|
||||
}
|
||||
|
||||
pub fn has_focus(&mut self) -> bool {
|
||||
unsafe {
|
||||
let view = self.inner.ns_view.as_mut().unwrap();
|
||||
let window: id = msg_send![view, window];
|
||||
if window == nil {
|
||||
return false;
|
||||
};
|
||||
let first_responder: id = msg_send![window, firstResponder];
|
||||
let is_key_window: BOOL = msg_send![window, isKeyWindow];
|
||||
let is_focused: BOOL = msg_send![view, isEqual: first_responder];
|
||||
is_key_window == YES && is_focused == YES
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus(&mut self) {
|
||||
unsafe {
|
||||
let view = self.inner.ns_view.as_mut().unwrap();
|
||||
let window: id = msg_send![view, window];
|
||||
if window != nil {
|
||||
msg_send![window, makeFirstResponder:view]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, size: Size) {
|
||||
if self.inner.open.get() {
|
||||
let size = NSSize::new(size.width.round(), size.height.round());
|
||||
|
||||
unsafe { NSView::setFrameSize(self.inner.ns_view, size) };
|
||||
|
||||
if let Some(ns_window) = self.inner.ns_window.get() {
|
||||
unsafe { NSWindow::setContentSize_(ns_window, size) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn physical_size(&self) -> crate::PhySize {
|
||||
unsafe {
|
||||
let frame: NSRect = NSView::frame(self.inner.ns_view);
|
||||
let ns_window: *mut Object = msg_send![self.inner.ns_view, window];
|
||||
let scale: f64 = if ns_window.is_null() {
|
||||
1.0
|
||||
} else {
|
||||
NSWindow::backingScaleFactor(ns_window)
|
||||
};
|
||||
crate::PhySize {
|
||||
width: (frame.size.width * scale).round() as u32,
|
||||
height: (frame.size.height * scale).round() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_mouse_cursor(&mut self, _mouse_cursor: MouseCursor) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
pub fn gl_context(&self) -> Option<&GlContext> {
|
||||
self.inner.gl_context.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
fn create_gl_context(ns_window: Option<id>, ns_view: id, config: GlConfig) -> GlContext {
|
||||
let mut handle = AppKitWindowHandle::empty();
|
||||
handle.ns_window = ns_window.unwrap_or(ptr::null_mut()) as *mut c_void;
|
||||
handle.ns_view = ns_view as *mut c_void;
|
||||
let handle = RawWindowHandle::AppKit(handle);
|
||||
|
||||
unsafe { GlContext::create(&handle, config).expect("Could not create OpenGL context") }
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct WindowState {
|
||||
pub(super) window_inner: WindowInner,
|
||||
window_handler: RefCell<Box<dyn WindowHandler>>,
|
||||
keyboard_state: KeyboardState,
|
||||
frame_timer: Cell<Option<CFRunLoopTimer>>,
|
||||
/// The last known window info for this window.
|
||||
pub window_info: Cell<WindowInfo>,
|
||||
|
||||
/// Events that will be triggered at the end of `window_handler`'s borrow.
|
||||
deferred_events: RefCell<VecDeque<Event>>,
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
/// Gets the `WindowState` held by a given `NSView`.
|
||||
///
|
||||
/// This method returns a cloned `Rc<WindowState>` rather than just a `&WindowState`, since the
|
||||
/// original `Rc<WindowState>` owned by the `NSView` can be dropped at any time
|
||||
/// (including during an event handler).
|
||||
pub(super) unsafe fn from_view(view: &Object) -> Rc<WindowState> {
|
||||
let state_ptr: *const c_void = *view.get_ivar(BASEVIEW_STATE_IVAR);
|
||||
|
||||
let state_rc = Rc::from_raw(state_ptr as *const WindowState);
|
||||
let state = Rc::clone(&state_rc);
|
||||
let _ = Rc::into_raw(state_rc);
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Trigger the event immediately and return the event status.
|
||||
/// Will panic if `window_handler` is already borrowed (see `trigger_deferrable_event`).
|
||||
pub(super) fn trigger_event(&self, event: Event) -> EventStatus {
|
||||
let mut window = crate::Window::new(Window { inner: &self.window_inner });
|
||||
let mut window_handler = self.window_handler.borrow_mut();
|
||||
let status = window_handler.on_event(&mut window, event);
|
||||
self.send_deferred_events(window_handler.as_mut());
|
||||
status
|
||||
}
|
||||
|
||||
/// Trigger the event immediately if `window_handler` can be borrowed mutably,
|
||||
/// otherwise add the event to a queue that will be cleared once `window_handler`'s mutable borrow ends.
|
||||
/// As this method might result in the event triggering asynchronously, it can't reliably return the event status.
|
||||
pub(super) fn trigger_deferrable_event(&self, event: Event) {
|
||||
if let Ok(mut window_handler) = self.window_handler.try_borrow_mut() {
|
||||
let mut window = crate::Window::new(Window { inner: &self.window_inner });
|
||||
window_handler.on_event(&mut window, event);
|
||||
self.send_deferred_events(window_handler.as_mut());
|
||||
} else {
|
||||
self.deferred_events.borrow_mut().push_back(event);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn trigger_frame(&self) {
|
||||
let mut window = crate::Window::new(Window { inner: &self.window_inner });
|
||||
let mut window_handler = self.window_handler.borrow_mut();
|
||||
window_handler.on_frame(&mut window);
|
||||
self.send_deferred_events(window_handler.as_mut());
|
||||
}
|
||||
|
||||
pub(super) fn keyboard_state(&self) -> &KeyboardState {
|
||||
&self.keyboard_state
|
||||
}
|
||||
|
||||
pub(super) fn process_native_key_event(&self, event: *mut Object) -> Option<KeyboardEvent> {
|
||||
self.keyboard_state.process_native_event(event)
|
||||
}
|
||||
|
||||
unsafe fn setup_timer(window_state_ptr: *const WindowState) {
|
||||
extern "C" fn timer_callback(_: *mut __CFRunLoopTimer, window_state_ptr: *mut c_void) {
|
||||
unsafe {
|
||||
let window_state = &*(window_state_ptr as *const WindowState);
|
||||
|
||||
window_state.trigger_frame();
|
||||
}
|
||||
}
|
||||
|
||||
let mut timer_context = CFRunLoopTimerContext {
|
||||
version: 0,
|
||||
info: window_state_ptr as *mut c_void,
|
||||
retain: None,
|
||||
release: None,
|
||||
copyDescription: None,
|
||||
};
|
||||
|
||||
let timer = CFRunLoopTimer::new(0.0, 0.015, 0, 0, timer_callback, &mut timer_context);
|
||||
|
||||
CFRunLoop::get_current().add_timer(&timer, kCFRunLoopDefaultMode);
|
||||
|
||||
(*window_state_ptr).frame_timer.set(Some(timer));
|
||||
}
|
||||
|
||||
fn send_deferred_events(&self, window_handler: &mut dyn WindowHandler) {
|
||||
let mut window = crate::Window::new(Window { inner: &self.window_inner });
|
||||
loop {
|
||||
let next_event = self.deferred_events.borrow_mut().pop_front();
|
||||
if let Some(event) = next_event {
|
||||
window_handler.on_event(&mut window, event);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> HasRawWindowHandle for Window<'a> {
|
||||
fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
self.inner.raw_window_handle()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a> HasRawDisplayHandle for Window<'a> {
|
||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_to_clipboard(string: &str) {
|
||||
unsafe {
|
||||
let pb = NSPasteboard::generalPasteboard(nil);
|
||||
|
||||
let ns_str = NSString::alloc(nil).init_str(string);
|
||||
|
||||
pb.clearContents();
|
||||
pb.setString_forType(ns_str, cocoa::appkit::NSPasteboardTypeString);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user