#![allow(non_snake_case)]
use std::ffi::c_void;
use std::mem;
use std::sync::{Arc, Mutex, Weak};
use std::time::Instant;
use block::ConcreteBlock;
use cocoa::appkit::{
CGFloat, NSApp, NSApplication, NSAutoresizingMaskOptions, NSBackingStoreBuffered, NSColor,
NSEvent, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowStyleMask,
};
use cocoa::base::{id, nil, BOOL, NO, YES};
use cocoa::foundation::{
NSArray, NSAutoreleasePool, NSInteger, NSPoint, NSRect, NSSize, NSString, NSUInteger,
};
use core_graphics::context::CGContextRef;
use foreign_types::ForeignTypeRef;
use objc::declare::ClassDecl;
use objc::rc::WeakPtr;
use objc::runtime::{Class, Object, Protocol, Sel};
use objc::{class, msg_send, sel, sel_impl};
use once_cell::sync::Lazy;
use tracing::{debug, error, info};
#[cfg(feature = "raw-win-handle")]
use raw_window_handle::{AppKitWindowHandle, HasRawWindowHandle, RawWindowHandle};
use crate::kurbo::{Insets, Point, Rect, Size, Vec2};
use crate::piet::{Piet, PietText, RenderContext};
use self::levels::{NSFloatingWindowLevel, NSNormalWindowLevel};
use super::appkit::{
NSRunLoopCommonModes, NSTrackingArea, NSTrackingAreaOptions, NSView as NSViewExt,
};
use super::application::Application;
use super::dialog;
use super::keyboard::{make_modifiers, KeyboardState};
use super::menu::Menu;
use super::text_input::NSRange;
use super::util::{assert_main_thread, make_nsstring};
use crate::common_util::IdleCallback;
use crate::dialog::{FileDialogOptions, FileDialogType};
use crate::keyboard_types::KeyState;
use crate::mouse::{Cursor, CursorDesc, MouseButton, MouseButtons, MouseEvent};
use crate::region::Region;
use crate::scale::Scale;
use crate::text::{Event, InputHandler};
use crate::window::{
FileDialogToken, IdleToken, TextFieldToken, TimerToken, WinHandler, WindowLevel, WindowState,
};
use crate::Error;
#[allow(non_upper_case_globals)]
const NSWindowDidBecomeKeyNotification: &str = "NSWindowDidBecomeKeyNotification";
#[allow(dead_code)]
#[allow(non_upper_case_globals)]
mod levels {
use crate::window::WindowLevel;
pub const NSModalPanelLevel: i32 = 24;
pub const NSNormalWindowLevel: i32 = 0;
pub const NSFloatingWindowLevel: i32 = 3;
pub const NSTornOffMenuWindowLevel: i32 = NSFloatingWindowLevel;
pub const NSSubmenuWindowLevel: i32 = NSFloatingWindowLevel;
pub const NSModalPanelWindowLevel: i32 = 8;
pub const NSStatusWindowLevel: i32 = 25;
pub const NSPopUpMenuWindowLevel: i32 = 101;
pub const NSScreenSaverWindowLevel: i32 = 1000;
pub fn as_raw_window_level(window_level: WindowLevel) -> i32 {
use WindowLevel::*;
match window_level {
AppWindow => NSNormalWindowLevel,
Tooltip(_) => NSFloatingWindowLevel,
DropDown(_) => NSFloatingWindowLevel,
Modal(_) => NSModalPanelWindowLevel,
}
}
}
#[derive(Clone)]
pub(crate) struct WindowHandle {
nsview: WeakPtr,
idle_queue: Weak<Mutex<Vec<IdleKind>>>,
}
impl PartialEq for WindowHandle {
fn eq(&self, other: &Self) -> bool {
match (self.idle_queue.upgrade(), other.idle_queue.upgrade()) {
(None, None) => true,
(Some(s), Some(o)) => std::sync::Arc::ptr_eq(&s, &o),
(_, _) => false,
}
}
}
impl Eq for WindowHandle {}
impl std::fmt::Debug for WindowHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.write_str("WindowHandle{\n")?;
f.write_str("}")?;
Ok(())
}
}
impl Default for WindowHandle {
fn default() -> Self {
WindowHandle {
nsview: unsafe { WeakPtr::new(nil) },
idle_queue: Default::default(),
}
}
}
pub(crate) struct WindowBuilder {
handler: Option<Box<dyn WinHandler>>,
title: String,
menu: Option<Menu>,
size: Size,
min_size: Option<Size>,
position: Option<Point>,
level: Option<WindowLevel>,
window_state: Option<WindowState>,
resizable: bool,
show_titlebar: bool,
transparent: bool,
always_on_top: bool,
}
#[derive(Clone)]
pub(crate) struct IdleHandle {
nsview: WeakPtr,
idle_queue: Weak<Mutex<Vec<IdleKind>>>,
}
#[derive(Debug)]
enum DeferredOp {
SetSize(Size),
SetPosition(Point),
}
enum IdleKind {
Callback(Box<dyn IdleCallback>),
Token(IdleToken),
DeferredOp(DeferredOp),
}
struct ViewState {
nsview: WeakPtr,
handler: Box<dyn WinHandler>,
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
focus_click: bool,
mouse_left: bool,
keyboard_state: KeyboardState,
text: PietText,
active_text_input: Option<TextFieldToken>,
parent: Option<crate::WindowHandle>,
}
#[derive(Clone, PartialEq, Eq)]
pub struct CustomCursor;
impl WindowBuilder {
pub fn new(_app: Application) -> WindowBuilder {
WindowBuilder {
handler: None,
title: String::new(),
menu: None,
size: Size::new(500., 400.),
min_size: None,
position: None,
level: None,
window_state: None,
resizable: true,
show_titlebar: true,
transparent: false,
always_on_top: false,
}
}
pub fn set_handler(&mut self, handler: Box<dyn WinHandler>) {
self.handler = Some(handler);
}
pub fn set_size(&mut self, size: Size) {
self.size = size;
}
pub fn set_min_size(&mut self, size: Size) {
self.min_size = Some(size);
}
pub fn resizable(&mut self, resizable: bool) {
self.resizable = resizable;
}
pub fn show_titlebar(&mut self, show_titlebar: bool) {
self.show_titlebar = show_titlebar;
}
pub fn set_transparent(&mut self, transparent: bool) {
self.transparent = transparent;
}
pub fn set_level(&mut self, level: WindowLevel) {
self.level = Some(level);
}
pub fn set_always_on_top(&mut self, always_on_top: bool) {
self.always_on_top = always_on_top;
}
pub fn set_position(&mut self, position: Point) {
self.position = Some(position)
}
pub fn set_window_state(&mut self, state: WindowState) {
self.window_state = Some(state);
}
pub fn set_title(&mut self, title: impl Into<String>) {
self.title = title.into();
}
pub fn set_menu(&mut self, menu: Menu) {
self.menu = Some(menu);
}
pub fn build(self) -> Result<WindowHandle, Error> {
assert_main_thread();
unsafe {
let mut style_mask = NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask;
if self.show_titlebar {
style_mask |= NSWindowStyleMask::NSTitledWindowMask;
}
if self.resizable {
style_mask |= NSWindowStyleMask::NSResizableWindowMask;
}
let screen_height = crate::Screen::get_display_rect().height();
let position = self.position.unwrap_or_else(|| Point::new(20., 20.));
let origin = NSPoint::new(position.x, screen_height - position.y - self.size.height);
let rect = NSRect::new(origin, NSSize::new(self.size.width, self.size.height));
let window: id = msg_send![WINDOW_CLASS.0, alloc];
let window = window.initWithContentRect_styleMask_backing_defer_(
rect,
style_mask,
NSBackingStoreBuffered,
NO,
);
if let Some(min_size) = self.min_size {
let size = NSSize::new(min_size.width, min_size.height);
window.setContentMinSize_(size);
}
if self.transparent {
window.setOpaque_(NO);
window.setBackgroundColor_(NSColor::clearColor(nil));
}
window.setTitle_(make_nsstring(&self.title));
let (view, idle_queue) = make_view(self.handler.expect("view"));
let content_view = window.contentView();
let frame = NSView::frame(content_view);
view.initWithFrame_(frame);
let rect = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
let opts = NSTrackingAreaOptions::MouseEnteredAndExited
| NSTrackingAreaOptions::MouseMoved
| NSTrackingAreaOptions::ActiveAlways
| NSTrackingAreaOptions::InVisibleRect;
let tracking_area = NSTrackingArea::alloc(nil)
.initWithRect_options_owner_userInfo(rect, opts, view, nil)
.autorelease();
view.addTrackingArea(tracking_area);
let () = msg_send![window, setDelegate: view];
if let Some(menu) = self.menu {
NSApp().setMainMenu_(menu.menu);
}
content_view.addSubview_(view);
let view_state: *mut c_void = *(*view).get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let mut handle = WindowHandle {
nsview: view_state.nsview.clone(),
idle_queue,
};
if let Some(window_state) = self.window_state {
handle.set_window_state(window_state);
}
if let Some(level) = self.level {
match &level {
WindowLevel::Tooltip(parent) => view_state.parent = Some(parent.clone()),
WindowLevel::DropDown(parent) => view_state.parent = Some(parent.clone()),
WindowLevel::Modal(parent) => view_state.parent = Some(parent.clone()),
_ => {}
}
handle.set_level(level);
}
if self.always_on_top {
handle.set_always_on_top(self.always_on_top);
}
let frame = NSView::frame(content_view);
view_state.handler.connect(&handle.clone().into());
view_state.handler.scale(Scale::default());
view_state
.handler
.size(Size::new(frame.size.width, frame.size.height));
Ok(handle)
}
}
}
struct ViewClass(*const Class);
unsafe impl Sync for ViewClass {}
unsafe impl Send for ViewClass {}
static VIEW_CLASS: Lazy<ViewClass> = Lazy::new(|| unsafe {
let mut decl = ClassDecl::new("DruidView", class!(NSView)).expect("View class defined");
decl.add_ivar::<*mut c_void>("viewState");
decl.add_method(
sel!(isFlipped),
isFlipped as extern "C" fn(&Object, Sel) -> BOOL,
);
extern "C" fn isFlipped(_this: &Object, _sel: Sel) -> BOOL {
YES
}
decl.add_method(
sel!(acceptsFirstResponder),
acceptsFirstResponder as extern "C" fn(&Object, Sel) -> BOOL,
);
extern "C" fn acceptsFirstResponder(_this: &Object, _sel: Sel) -> BOOL {
YES
}
decl.add_method(
sel!(acceptsFirstMouse:),
acceptsFirstMouse as extern "C" fn(&Object, Sel, id) -> BOOL,
);
extern "C" fn acceptsFirstMouse(this: &Object, _sel: Sel, _nsevent: id) -> BOOL {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.focus_click = true;
}
YES
}
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
extern "C" fn dealloc(this: &Object, _sel: Sel) {
info!("view is dealloc'ed");
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
drop(Box::from_raw(view_state as *mut ViewState));
}
}
decl.add_method(
sel!(windowDidBecomeKey:),
window_did_become_key as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(windowDidResignKey:),
window_did_resign_key as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(setFrameSize:),
set_frame_size as extern "C" fn(&mut Object, Sel, NSSize),
);
decl.add_method(
sel!(mouseDown:),
mouse_down_left as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(rightMouseDown:),
mouse_down_right as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(otherMouseDown:),
mouse_down_other as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(mouseUp:),
mouse_up_left as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(rightMouseUp:),
mouse_up_right as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(otherMouseUp:),
mouse_up_other as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(mouseMoved:),
mouse_move as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(mouseDragged:),
mouse_move as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(otherMouseDragged:),
mouse_move as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(mouseEntered:),
mouse_enter as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(mouseExited:),
mouse_leave as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(scrollWheel:),
scroll_wheel as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(magnifyWithEvent:),
pinch_event as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(keyDown:),
key_down as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&mut Object, Sel, id));
decl.add_method(
sel!(flagsChanged:),
mods_changed as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(drawRect:),
draw_rect as extern "C" fn(&mut Object, Sel, NSRect),
);
decl.add_method(sel!(runIdle), run_idle as extern "C" fn(&mut Object, Sel));
decl.add_method(
sel!(viewWillDraw),
view_will_draw as extern "C" fn(&mut Object, Sel),
);
decl.add_method(sel!(redraw), redraw as extern "C" fn(&mut Object, Sel));
decl.add_method(
sel!(handleTimer:),
handle_timer as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(handleMenuItem:),
handle_menu_item as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(showContextMenu:),
show_context_menu as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(windowShouldClose:),
window_should_close as extern "C" fn(&mut Object, Sel, id) -> BOOL,
);
decl.add_method(
sel!(windowWillClose:),
window_will_close as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(hasMarkedText),
super::text_input::has_marked_text as extern "C" fn(&mut Object, Sel) -> BOOL,
);
decl.add_method(
sel!(markedRange),
super::text_input::marked_range as extern "C" fn(&mut Object, Sel) -> NSRange,
);
decl.add_method(
sel!(selectedRange),
super::text_input::selected_range as extern "C" fn(&mut Object, Sel) -> NSRange,
);
decl.add_method(
sel!(setMarkedText:selectedRange:replacementRange:),
super::text_input::set_marked_text as extern "C" fn(&mut Object, Sel, id, NSRange, NSRange),
);
decl.add_method(
sel!(unmarkText),
super::text_input::unmark_text as extern "C" fn(&mut Object, Sel),
);
decl.add_method(
sel!(validAttributesForMarkedText),
super::text_input::valid_attributes_for_marked_text
as extern "C" fn(&mut Object, Sel) -> id,
);
decl.add_method(
sel!(attributedSubstringForProposedRange:actualRange:),
super::text_input::attributed_substring_for_proposed_range
as extern "C" fn(&mut Object, Sel, NSRange, *mut c_void) -> id,
);
decl.add_method(
sel!(insertText:replacementRange:),
super::text_input::insert_text as extern "C" fn(&mut Object, Sel, id, NSRange),
);
decl.add_method(
sel!(characterIndexForPoint:),
super::text_input::character_index_for_point
as extern "C" fn(&mut Object, Sel, NSPoint) -> NSUInteger,
);
decl.add_method(
sel!(firstRectForCharacterRange:actualRange:),
super::text_input::first_rect_for_character_range
as extern "C" fn(&mut Object, Sel, NSRange, *mut c_void) -> NSRect,
);
decl.add_method(
sel!(doCommandBySelector:),
super::text_input::do_command_by_selector as extern "C" fn(&mut Object, Sel, Sel),
);
let protocol = Protocol::get("NSTextInputClient").unwrap();
decl.add_protocol(protocol);
ViewClass(decl.register())
});
pub(super) fn with_edit_lock_from_window<R>(
this: &mut Object,
mutable: bool,
f: impl FnOnce(Box<dyn InputHandler>) -> R,
) -> Option<R> {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
&mut (*view_state)
};
let input_token = view_state.active_text_input?;
let handler = view_state.handler.acquire_input_lock(input_token, mutable);
let r = f(handler);
view_state.handler.release_input_lock(input_token);
Some(r)
}
fn make_view(handler: Box<dyn WinHandler>) -> (id, Weak<Mutex<Vec<IdleKind>>>) {
let idle_queue = Arc::new(Mutex::new(Vec::new()));
let queue_handle = Arc::downgrade(&idle_queue);
unsafe {
let view: id = msg_send![VIEW_CLASS.0, new];
let nsview = WeakPtr::new(view);
let keyboard_state = KeyboardState::new();
let state = ViewState {
nsview,
handler,
idle_queue,
focus_click: false,
mouse_left: true,
keyboard_state,
text: PietText::new_with_unique_state(),
active_text_input: None,
parent: None,
};
let state_ptr = Box::into_raw(Box::new(state));
(*view).set_ivar("viewState", state_ptr as *mut c_void);
let options: NSAutoresizingMaskOptions = NSViewWidthSizable | NSViewHeightSizable;
view.setAutoresizingMask_(options);
(view.autorelease(), queue_handle)
}
}
struct WindowClass(*const Class);
unsafe impl Sync for WindowClass {}
unsafe impl Send for WindowClass {}
static WINDOW_CLASS: Lazy<WindowClass> = Lazy::new(|| unsafe {
let mut decl = ClassDecl::new("DruidWindow", class!(NSWindow)).expect("Window class defined");
decl.add_method(
sel!(canBecomeKeyWindow),
canBecomeKeyWindow as extern "C" fn(&Object, Sel) -> BOOL,
);
extern "C" fn canBecomeKeyWindow(_this: &Object, _sel: Sel) -> BOOL {
YES
}
WindowClass(decl.register())
});
extern "C" fn set_frame_size(this: &mut Object, _: Sel, size: NSSize) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.size(Size::new(size.width, size.height));
let superclass = msg_send![this, superclass];
let () = msg_send![super(this, superclass), setFrameSize: size];
}
}
fn mouse_event(
nsevent: id,
view: id,
count: u8,
focus: bool,
button: MouseButton,
wheel_delta: Vec2,
) -> MouseEvent {
unsafe {
let point = nsevent.locationInWindow();
let view_point = view.convertPoint_fromView_(point, nil);
let pos = Point::new(view_point.x, view_point.y);
let buttons = get_mouse_buttons(NSEvent::pressedMouseButtons(nsevent));
let modifiers = make_modifiers(nsevent.modifierFlags());
MouseEvent {
pos,
buttons,
mods: modifiers,
count,
focus,
button,
wheel_delta,
}
}
}
fn get_mouse_button(button: NSInteger) -> Option<MouseButton> {
match button {
0 => Some(MouseButton::Left),
1 => Some(MouseButton::Right),
2 => Some(MouseButton::Middle),
3 => Some(MouseButton::X1),
4 => Some(MouseButton::X2),
_ => None,
}
}
fn get_mouse_buttons(mask: NSUInteger) -> MouseButtons {
let mut buttons = MouseButtons::new();
if mask & 1 != 0 {
buttons.insert(MouseButton::Left);
}
if mask & 1 << 1 != 0 {
buttons.insert(MouseButton::Right);
}
if mask & 1 << 2 != 0 {
buttons.insert(MouseButton::Middle);
}
if mask & 1 << 3 != 0 {
buttons.insert(MouseButton::X1);
}
if mask & 1 << 4 != 0 {
buttons.insert(MouseButton::X2);
}
buttons
}
extern "C" fn mouse_down_left(this: &mut Object, _: Sel, nsevent: id) {
mouse_down(this, nsevent, MouseButton::Left);
}
extern "C" fn mouse_down_right(this: &mut Object, _: Sel, nsevent: id) {
mouse_down(this, nsevent, MouseButton::Right);
}
extern "C" fn mouse_down_other(this: &mut Object, _: Sel, nsevent: id) {
unsafe {
if let Some(button) = get_mouse_button(nsevent.buttonNumber()) {
mouse_down(this, nsevent, button);
}
}
}
fn mouse_down(this: &mut Object, nsevent: id, button: MouseButton) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let count = nsevent.clickCount() as u8;
let focus = view_state.focus_click && button == MouseButton::Left;
let event = mouse_event(nsevent, this as id, count, focus, button, Vec2::ZERO);
view_state.handler.mouse_down(&event);
}
}
extern "C" fn mouse_up_left(this: &mut Object, _: Sel, nsevent: id) {
mouse_up(this, nsevent, MouseButton::Left);
}
extern "C" fn mouse_up_right(this: &mut Object, _: Sel, nsevent: id) {
mouse_up(this, nsevent, MouseButton::Right);
}
extern "C" fn mouse_up_other(this: &mut Object, _: Sel, nsevent: id) {
unsafe {
if let Some(button) = get_mouse_button(nsevent.buttonNumber()) {
mouse_up(this, nsevent, button);
}
}
}
fn mouse_up(this: &mut Object, nsevent: id, button: MouseButton) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let focus = if view_state.focus_click && button == MouseButton::Left {
view_state.focus_click = false;
true
} else {
false
};
let event = mouse_event(nsevent, this as id, 0, focus, button, Vec2::ZERO);
view_state.handler.mouse_up(&event);
if view_state.mouse_left && event.buttons.is_empty() {
view_state.handler.mouse_leave();
}
}
}
extern "C" fn mouse_move(this: &mut Object, _: Sel, nsevent: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let event = mouse_event(nsevent, this as id, 0, false, MouseButton::None, Vec2::ZERO);
view_state.handler.mouse_move(&event);
}
}
extern "C" fn mouse_enter(this: &mut Object, _sel: Sel, nsevent: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.mouse_left = false;
let event = mouse_event(nsevent, this, 0, false, MouseButton::None, Vec2::ZERO);
view_state.handler.mouse_move(&event);
}
}
extern "C" fn mouse_leave(this: &mut Object, _: Sel, _nsevent: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.mouse_left = true;
view_state.handler.mouse_leave();
}
}
extern "C" fn scroll_wheel(this: &mut Object, _: Sel, nsevent: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let (dx, dy) = {
let dx = -nsevent.scrollingDeltaX();
let dy = -nsevent.scrollingDeltaY();
if nsevent.hasPreciseScrollingDeltas() == cocoa::base::YES {
(dx, dy)
} else {
(dx * 32.0, dy * 32.0)
}
};
let event = mouse_event(
nsevent,
this as id,
0,
false,
MouseButton::None,
Vec2::new(dx, dy),
);
view_state.handler.wheel(&event);
}
}
extern "C" fn pinch_event(this: &mut Object, _: Sel, nsevent: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let delta: CGFloat = msg_send![nsevent, magnification];
view_state.handler.zoom(delta);
}
}
extern "C" fn key_down(this: &mut Object, _: Sel, nsevent: id) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
&mut *(view_state as *mut ViewState)
};
if let Some(event) = view_state.keyboard_state.process_native_event(nsevent) {
if !view_state.handler.key_down(event) {
unsafe {
let events = NSArray::arrayWithObjects(nil, &[nsevent]);
let _: () = msg_send![*view_state.nsview.load(), interpretKeyEvents: events];
}
}
}
}
extern "C" fn key_up(this: &mut Object, _: Sel, nsevent: id) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
&mut *(view_state as *mut ViewState)
};
if let Some(event) = view_state.keyboard_state.process_native_event(nsevent) {
view_state.handler.key_up(event);
}
}
extern "C" fn mods_changed(this: &mut Object, _: Sel, nsevent: id) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
&mut *(view_state as *mut ViewState)
};
if let Some(event) = view_state.keyboard_state.process_native_event(nsevent) {
if event.state == KeyState::Down {
view_state.handler.key_down(event);
} else {
view_state.handler.key_up(event);
}
}
}
extern "C" fn view_will_draw(this: &mut Object, _: Sel) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.prepare_paint();
}
}
extern "C" fn draw_rect(this: &mut Object, _: Sel, dirtyRect: NSRect) {
unsafe {
let context: id = msg_send![class![NSGraphicsContext], currentContext];
let cgcontext_ptr: *mut <CGContextRef as ForeignTypeRef>::CType =
msg_send![context, CGContext];
let cgcontext_ref = CGContextRef::from_ptr_mut(cgcontext_ptr);
let rect = Rect::from_origin_size(
(dirtyRect.origin.x, dirtyRect.origin.y),
(dirtyRect.size.width, dirtyRect.size.height),
);
let invalid = Region::from(rect);
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
let mut piet_ctx = Piet::new_y_down(cgcontext_ref, Some(view_state.text.clone()));
view_state.handler.paint(&mut piet_ctx, &invalid);
if let Err(e) = piet_ctx.finish() {
error!("{}", e)
}
let superclass = msg_send![this, superclass];
let () = msg_send![super(this, superclass), drawRect: dirtyRect];
}
}
fn run_deferred(this: &mut Object, view_state: &mut ViewState, op: DeferredOp) {
match op {
DeferredOp::SetSize(size) => set_size_deferred(this, view_state, size),
DeferredOp::SetPosition(pos) => set_position_deferred(this, view_state, pos),
}
}
fn set_size_deferred(this: &mut Object, _view_state: &mut ViewState, size: Size) {
unsafe {
let window: id = msg_send![this, window];
let current_frame: NSRect = msg_send![window, frame];
let mut new_frame = current_frame;
new_frame.origin.y -= size.height - current_frame.size.height;
new_frame.size.width = size.width;
new_frame.size.height = size.height;
let () = msg_send![window, setFrame: new_frame display: YES];
}
}
fn set_position_deferred(this: &mut Object, _view_state: &mut ViewState, position: Point) {
unsafe {
let window: id = msg_send![this, window];
let frame: NSRect = msg_send![window, frame];
let mut new_frame = frame;
new_frame.origin.x = position.x;
let screen_height = crate::Screen::get_display_rect().height();
new_frame.origin.y = screen_height - position.y - frame.size.height; let () = msg_send![window, setFrame: new_frame display: YES];
debug!("set_position_deferred {:?}", position);
}
}
extern "C" fn run_idle(this: &mut Object, _: Sel) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
&mut *(view_state as *mut ViewState)
};
let queue: Vec<_> = mem::take(&mut view_state.idle_queue.lock().expect("queue"));
for item in queue {
match item {
IdleKind::Callback(it) => it.call(&mut *view_state.handler),
IdleKind::Token(it) => {
view_state.handler.as_mut().idle(it);
}
IdleKind::DeferredOp(op) => run_deferred(this, view_state, op),
}
}
}
extern "C" fn redraw(this: &mut Object, _: Sel) {
unsafe {
let () = msg_send![this as *const _, setNeedsDisplay: YES];
}
}
extern "C" fn handle_timer(this: &mut Object, _: Sel, timer: id) {
let view_state = unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
&mut *(view_state as *mut ViewState)
};
let token = unsafe {
let user_info: id = msg_send![timer, userInfo];
msg_send![user_info, unsignedIntValue]
};
view_state.handler.timer(TimerToken::from_raw(token));
}
extern "C" fn handle_menu_item(this: &mut Object, _: Sel, item: id) {
unsafe {
let tag: isize = msg_send![item, tag];
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.command(tag as u32);
}
}
extern "C" fn show_context_menu(this: &mut Object, _: Sel, item: id) {
unsafe {
let window: id = msg_send![this as *const _, window];
let mut location: NSPoint = msg_send![window, mouseLocationOutsideOfEventStream];
let bounds: NSRect = msg_send![this as *const _, bounds];
location.y = bounds.size.height - location.y;
let _: BOOL = msg_send![item, popUpMenuPositioningItem: nil atLocation: location inView: this as *const _];
}
}
extern "C" fn window_did_become_key(this: &mut Object, _: Sel, _notification: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.got_focus();
}
}
extern "C" fn window_did_resign_key(this: &mut Object, _: Sel, _notification: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.lost_focus();
}
}
extern "C" fn window_should_close(this: &mut Object, _: Sel, _window: id) -> BOOL {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.request_close();
NO
}
}
extern "C" fn window_will_close(this: &mut Object, _: Sel, _notification: id) {
unsafe {
let view_state: *mut c_void = *this.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
view_state.handler.destroy();
}
}
impl WindowHandle {
pub fn show(&self) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let notif_center_class = class!(NSNotificationCenter);
let notif_string = NSString::alloc(nil)
.init_str(NSWindowDidBecomeKeyNotification)
.autorelease();
let notif_center: id = msg_send![notif_center_class, defaultCenter];
let () = msg_send![notif_center, addObserver:*self.nsview.load() selector: sel!(windowDidBecomeKey:) name: notif_string object: window];
window.makeKeyAndOrderFront_(nil)
}
}
pub fn close(&self) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let () = msg_send![window, performSelectorOnMainThread: sel!(close) withObject: nil waitUntilDone: NO];
}
}
pub fn hide(&self) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let () = msg_send![window, orderOut: self];
}
}
pub fn bring_to_front_and_focus(&self) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let () = msg_send![window, performSelectorOnMainThread: sel!(makeKeyAndOrderFront:) withObject: nil waitUntilDone: NO];
}
}
pub fn request_anim_frame(&self) {
unsafe {
let () = msg_send![*self.nsview.load(), performSelectorOnMainThread: sel!(redraw)
withObject: nil waitUntilDone: NO];
}
}
pub fn invalidate(&self) {
unsafe {
let () = msg_send![*self.nsview.load(), setNeedsDisplay: YES];
}
}
pub fn invalidate_rect(&self, rect: Rect) {
let rect = NSRect::new(
NSPoint::new(rect.x0, rect.y0),
NSSize::new(rect.width(), rect.height()),
);
unsafe {
let () = msg_send![*self.nsview.load(), setNeedsDisplayInRect: rect];
}
}
pub fn set_cursor(&mut self, cursor: &Cursor) {
unsafe {
let nscursor = class!(NSCursor);
#[allow(deprecated)]
let cursor: id = match cursor {
Cursor::Arrow => msg_send![nscursor, arrowCursor],
Cursor::IBeam => msg_send![nscursor, IBeamCursor],
Cursor::Pointer => msg_send![nscursor, pointingHandCursor],
Cursor::Crosshair => msg_send![nscursor, crosshairCursor],
Cursor::OpenHand => msg_send![nscursor, openHandCursor],
Cursor::NotAllowed => msg_send![nscursor, operationNotAllowedCursor],
Cursor::ResizeLeftRight => msg_send![nscursor, resizeLeftRightCursor],
Cursor::ResizeUpDown => msg_send![nscursor, resizeUpDownCursor],
Cursor::Custom(_) => msg_send![nscursor, arrowCursor],
};
let () = msg_send![cursor, set];
}
}
pub fn make_cursor(&self, _cursor_desc: &CursorDesc) -> Option<Cursor> {
tracing::warn!("Custom cursors are not yet supported in the macOS backend");
None
}
pub fn request_timer(&self, deadline: std::time::Instant) -> TimerToken {
let ti = time_interval_from_deadline(deadline);
let token = TimerToken::next();
unsafe {
let nstimer = class!(NSTimer);
let nsnumber = class!(NSNumber);
let user_info: id = msg_send![nsnumber, numberWithUnsignedInteger: token.into_raw()];
let selector = sel!(handleTimer:);
let view = self.nsview.load();
let timer: id = msg_send![nstimer, timerWithTimeInterval: ti target: view selector: selector userInfo: user_info repeats: NO];
let runloop: id = msg_send![class!(NSRunLoop), currentRunLoop];
let () = msg_send![runloop, addTimer: timer forMode: NSRunLoopCommonModes];
}
token
}
pub fn text(&self) -> PietText {
let view = self.nsview.load();
unsafe {
if let Some(view) = (*view).as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
(*(state as *mut ViewState)).text.clone()
} else {
PietText::new_with_unique_state()
}
}
}
pub fn add_text_field(&self) -> TextFieldToken {
TextFieldToken::next()
}
pub fn remove_text_field(&self, token: TextFieldToken) {
let view = self.nsview.load();
unsafe {
if let Some(view) = (*view).as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if state.active_text_input == Some(token) {
state.active_text_input = None;
}
}
}
}
pub fn set_focused_text_field(&self, active_field: Option<TextFieldToken>) {
unsafe {
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if let Some(old_field) = state.active_text_input {
self.update_text_field(old_field, Event::Reset);
}
state.active_text_input = active_field;
if let Some(new_field) = active_field {
self.update_text_field(new_field, Event::Reset);
}
}
}
}
pub fn update_text_field(&self, token: TextFieldToken, update: Event) {
unsafe {
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if state.active_text_input != Some(token) {
return;
}
match update {
Event::LayoutChanged => {
let input_context: id = msg_send![*self.nsview.load(), inputContext];
let _: () = msg_send![input_context, invalidateCharacterCoordinates];
}
Event::Reset | Event::SelectionChanged => {
let input_context: id = msg_send![*self.nsview.load(), inputContext];
let _: () = msg_send![input_context, discardMarkedText];
let mut edit_lock = state.handler.acquire_input_lock(token, true);
edit_lock.set_composition_range(None);
state.handler.release_input_lock(token);
}
}
}
}
}
pub fn open_file(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
Some(self.open_save_impl(FileDialogType::Open, options))
}
pub fn save_as(&mut self, options: FileDialogOptions) -> Option<FileDialogToken> {
Some(self.open_save_impl(FileDialogType::Save, options))
}
fn open_save_impl(&mut self, ty: FileDialogType, opts: FileDialogOptions) -> FileDialogToken {
let token = FileDialogToken::next();
let self_clone = self.clone();
unsafe {
let panel = dialog::build_panel(ty, opts.clone());
let block = ConcreteBlock::new(move |response: dialog::NSModalResponse| {
let url = dialog::get_file_info(panel, opts.clone(), response);
let view = self_clone.nsview.load();
if let Some(view) = (*view).as_ref() {
let view_state: *mut c_void = *view.get_ivar("viewState");
let view_state = &mut *(view_state as *mut ViewState);
if ty == FileDialogType::Open {
view_state.handler.open_file(token, url);
} else if ty == FileDialogType::Save {
view_state.handler.save_as(token, url);
}
}
});
let block = block.copy();
let () = msg_send![panel, beginWithCompletionHandler: block];
}
token
}
pub fn set_title(&self, title: &str) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let title = make_nsstring(title);
window.setTitle_(title);
}
}
pub fn show_titlebar(&self, _show_titlebar: bool) {}
pub fn set_position(&self, mut position: Point) {
unsafe {
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position += (pos.x, pos.y)
}
}
}
self.defer(DeferredOp::SetPosition(position))
}
pub fn get_position(&self) -> Point {
unsafe {
let screen_height = crate::Screen::get_display_rect().height();
let window: id = msg_send![*self.nsview.load(), window];
let current_frame: NSRect = msg_send![window, frame];
let mut position = Point::new(
current_frame.origin.x,
screen_height - current_frame.origin.y - current_frame.size.height,
);
if let Some(view) = self.nsview.load().as_ref() {
let state: *mut c_void = *view.get_ivar("viewState");
let state = &mut (*(state as *mut ViewState));
if let Some(parent_state) = &state.parent {
let pos = (*parent_state).get_position();
position -= (pos.x, pos.y)
}
}
position
}
}
pub fn content_insets(&self) -> Insets {
unsafe {
let screen_height = crate::Screen::get_display_rect().height();
let window: id = msg_send![*self.nsview.load(), window];
let clr: NSRect = msg_send![window, contentLayoutRect];
let window_frame_r: NSRect = NSWindow::frame(window);
let content_frame_r: NSRect = NSWindow::convertRectToScreen_(window, clr);
let window_frame_rk = Rect::from_origin_size(
(
window_frame_r.origin.x,
screen_height - window_frame_r.origin.y - window_frame_r.size.height,
),
(window_frame_r.size.width, window_frame_r.size.height),
);
let content_frame_rk = Rect::from_origin_size(
(
content_frame_r.origin.x,
screen_height - content_frame_r.origin.y - content_frame_r.size.height,
),
(content_frame_r.size.width, content_frame_r.size.height),
);
window_frame_rk - content_frame_rk
}
}
pub fn set_input_region(&self, _region: Option<Region>) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
window.invalidateShadow();
}
}
pub fn set_always_on_top(&self, always_on_top: bool) {
unsafe {
let level = if always_on_top {
NSFloatingWindowLevel
} else {
NSNormalWindowLevel
};
let window: id = msg_send![*self.nsview.load(), window];
let () = msg_send![window, setLevel: level];
}
}
fn set_level(&self, level: WindowLevel) {
unsafe {
let level = levels::as_raw_window_level(level);
let window: id = msg_send![*self.nsview.load(), window];
let () = msg_send![window, setLevel: level];
}
}
pub fn set_size(&self, size: Size) {
self.defer(DeferredOp::SetSize(size));
}
pub fn get_size(&self) -> Size {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let current_frame: NSRect = msg_send![window, frame];
Size::new(current_frame.size.width, current_frame.size.height)
}
}
pub fn get_window_state(&self) -> WindowState {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let isMin: BOOL = msg_send![window, isMiniaturized];
if isMin != NO {
return WindowState::Minimized;
}
let isZoomed: BOOL = msg_send![window, isZoomed];
if isZoomed != NO {
return WindowState::Maximized;
}
}
WindowState::Restored
}
pub fn set_window_state(&mut self, state: WindowState) {
let cur_state = self.get_window_state();
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
match (state, cur_state) {
(s1, s2) if s1 == s2 => (),
(WindowState::Minimized, _) => {
let () = msg_send![window, performMiniaturize: self];
}
(WindowState::Maximized, _) => {
let () = msg_send![window, performZoom: self];
}
(WindowState::Restored, WindowState::Maximized) => {
let () = msg_send![window, performZoom: self];
}
(WindowState::Restored, WindowState::Minimized) => {
let () = msg_send![window, deminiaturize: self];
}
(WindowState::Restored, WindowState::Restored) => {} }
}
}
pub fn handle_titlebar(&self, _val: bool) {
tracing::warn!("WindowHandle::handle_titlebar is currently unimplemented for Mac.");
}
pub fn resizable(&self, resizable: bool) {
unsafe {
let window: id = msg_send![*self.nsview.load(), window];
let mut style_mask: NSWindowStyleMask = window.styleMask();
if resizable {
style_mask |= NSWindowStyleMask::NSResizableWindowMask;
} else {
style_mask &= !NSWindowStyleMask::NSResizableWindowMask;
}
window.setStyleMask_(style_mask);
}
}
pub fn set_menu(&self, menu: Menu) {
unsafe {
NSApp().setMainMenu_(menu.menu);
}
}
pub fn show_context_menu(&self, menu: Menu, _pos: Point) {
unsafe {
let () = msg_send![*self.nsview.load(), performSelectorOnMainThread: sel!(showContextMenu:) withObject: menu.menu waitUntilDone: NO];
}
}
fn defer(&self, op: DeferredOp) {
if let Some(i) = self.get_idle_handle() {
i.add_idle(IdleKind::DeferredOp(op))
}
}
pub fn get_idle_handle(&self) -> Option<IdleHandle> {
if self.nsview.load().is_null() {
None
} else {
Some(IdleHandle {
nsview: self.nsview.clone(),
idle_queue: self.idle_queue.clone(),
})
}
}
pub fn get_scale(&self) -> Result<Scale, Error> {
let scale_factor: CGFloat = unsafe { msg_send![*self.nsview.load(), backingScaleFactor] };
Ok(Scale::new(scale_factor, scale_factor))
}
}
#[cfg(feature = "raw-win-handle")]
unsafe impl HasRawWindowHandle for WindowHandle {
fn raw_window_handle(&self) -> RawWindowHandle {
let nsv = self.nsview.load();
let mut handle = AppKitWindowHandle::empty();
handle.ns_view = *nsv as *mut _;
RawWindowHandle::AppKit(handle)
}
}
unsafe impl Send for IdleHandle {}
unsafe impl Sync for IdleHandle {}
impl IdleHandle {
fn add_idle(&self, idle: IdleKind) {
if let Some(queue) = self.idle_queue.upgrade() {
let mut queue = queue.lock().expect("queue lock");
if queue.is_empty() {
unsafe {
let nsview = self.nsview.load();
let () = msg_send!(*nsview, performSelectorOnMainThread: sel!(runIdle)
withObject: nil waitUntilDone: NO);
}
}
queue.push(idle);
}
}
pub fn add_idle_callback<F>(&self, callback: F)
where
F: FnOnce(&mut dyn WinHandler) + Send + 'static,
{
self.add_idle(IdleKind::Callback(Box::new(callback)));
}
pub fn add_idle_token(&self, token: IdleToken) {
self.add_idle(IdleKind::Token(token));
}
}
fn time_interval_from_deadline(deadline: std::time::Instant) -> f64 {
let now = Instant::now();
if now >= deadline {
0.0
} else {
let t = deadline - now;
let secs = t.as_secs() as f64;
let subsecs = f64::from(t.subsec_micros()) * 0.000_001;
secs + subsecs
}
}