This commit is contained in:
Nathan Sobo
2021-02-20 16:05:36 -07:00
parent b400449a58
commit 222f9d373d
16 changed files with 1164 additions and 179 deletions
+114
View File
@@ -0,0 +1,114 @@
// #[cfg(not(test))]
use anyhow::{anyhow, Result};
use async_task::Runnable;
use smol::prelude::*;
use smol::{channel, Executor};
use std::rc::Rc;
use std::sync::Arc;
use std::{marker::PhantomData, thread};
use crate::platform;
pub enum Foreground {
Platform {
dispatcher: Arc<dyn platform::Dispatcher>,
_not_send_or_sync: PhantomData<Rc<()>>,
},
Test(smol::LocalExecutor<'static>),
}
pub enum ForegroundTask<T> {
Platform(async_task::Task<T>),
Test(smol::Task<T>),
}
pub struct Background {
executor: Arc<smol::Executor<'static>>,
_stop: channel::Sender<()>,
}
pub type BackgroundTask<T> = smol::Task<T>;
impl Foreground {
pub fn platform(dispatcher: Arc<dyn platform::Dispatcher>) -> Result<Self> {
if dispatcher.is_main_thread() {
Ok(Self::Platform {
dispatcher,
_not_send_or_sync: PhantomData,
})
} else {
Err(anyhow!("must be constructed on main thread"))
}
}
pub fn test() -> Self {
Self::Test(smol::LocalExecutor::new())
}
pub fn spawn<T: 'static>(
&self,
future: impl Future<Output = T> + 'static,
) -> ForegroundTask<T> {
match self {
Self::Platform { dispatcher, .. } => {
let dispatcher = dispatcher.clone();
let schedule = move |runnable: Runnable| dispatcher.run_on_main_thread(runnable);
let (runnable, task) = async_task::spawn_local(future, schedule);
runnable.schedule();
ForegroundTask::Platform(task)
}
Self::Test(executor) => ForegroundTask::Test(executor.spawn(future)),
}
}
pub async fn run<T>(&self, future: impl Future<Output = T>) -> T {
match self {
Self::Platform { .. } => panic!("you can't call run on a platform foreground executor"),
Self::Test(executor) => executor.run(future).await,
}
}
}
impl<T> ForegroundTask<T> {
pub fn detach(self) {
match self {
Self::Platform(task) => task.detach(),
Self::Test(task) => task.detach(),
}
}
pub async fn cancel(self) -> Option<T> {
match self {
Self::Platform(task) => task.cancel().await,
Self::Test(task) => task.cancel().await,
}
}
}
impl Background {
pub fn new() -> Self {
let executor = Arc::new(Executor::new());
let stop = channel::unbounded::<()>();
for i in 0..num_cpus::get() {
let executor = executor.clone();
let stop = stop.1.clone();
thread::Builder::new()
.name(format!("background-executor-{}", i))
.spawn(move || smol::block_on(executor.run(stop.recv())))
.unwrap();
}
Self {
executor,
_stop: stop.0,
}
}
pub fn spawn<T>(&self, future: impl Send + Future<Output = T> + 'static) -> BackgroundTask<T>
where
T: 'static + Send,
{
self.executor.spawn(future)
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod executor;
pub mod keymap;
pub mod platform;
+26 -163
View File
@@ -1,176 +1,39 @@
use super::Event;
pub use cocoa::foundation::NSSize;
use cocoa::{
base::{id, nil},
foundation::{NSArray, NSAutoreleasePool, NSString},
};
use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use std::{
ffi::CStr,
os::raw::{c_char, c_void},
path::PathBuf,
};
use super::{BoolExt as _, Dispatcher, Window};
use crate::{executor, platform};
use anyhow::Result;
use cocoa::base::id;
use objc::{class, msg_send, sel, sel_impl};
use std::{rc::Rc, sync::Arc};
#[derive(Default)]
pub struct App {
finish_launching_callback: Option<Box<dyn FnOnce()>>,
become_active_callback: Option<Box<dyn FnMut()>>,
resign_active_callback: Option<Box<dyn FnMut()>>,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
dispatcher: Arc<Dispatcher>,
}
const RUST_WRAPPER_IVAR_NAME: &'static str = "rustWrapper";
impl App {
pub fn new() -> Self {
Self {
dispatcher: Arc::new(Dispatcher),
}
}
}
impl super::App for App {
fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
self.finish_launching_callback = Some(Box::new(callback));
self
impl platform::App for App {
fn dispatcher(&self) -> Arc<dyn platform::Dispatcher> {
self.dispatcher.clone()
}
fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
self.become_active_callback = Some(Box::new(callback));
self
}
fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
self.resign_active_callback = Some(Box::new(callback));
self
}
fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
self.event_callback = Some(Box::new(callback));
self
}
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
self.open_files_callback = Some(Box::new(callback));
self
}
fn run(self) {
fn activate(&self, ignoring_other_apps: bool) {
unsafe {
let self_ptr = Box::into_raw(Box::new(self));
let pool = NSAutoreleasePool::new(nil);
let app: id = msg_send![build_app_class(), sharedApplication];
(*app).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
let app_delegate: id = msg_send![build_app_delegate_class(), new];
(*app_delegate).set_ivar(RUST_WRAPPER_IVAR_NAME, self_ptr as *mut c_void);
let _: () = msg_send![app, setDelegate: app_delegate];
let _: () = msg_send![app, run];
let _: () = msg_send![pool, drain];
// App is done running when we get here, so we can reinstantiate the Box and drop it.
Box::from_raw(self_ptr);
}
}
}
fn build_app_class() -> *const Class {
unsafe {
let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
decl.add_method(
sel!(sendEvent:),
send_event as extern "C" fn(&Object, Sel, id),
);
decl.register()
}
}
fn build_app_delegate_class() -> *const Class {
unsafe {
let superclass = class!(NSResponder);
let mut decl = ClassDecl::new("GPUIApplicationDelegate", superclass).unwrap();
decl.add_ivar::<*mut c_void>(RUST_WRAPPER_IVAR_NAME);
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(applicationDidResignActive:),
did_resign_active as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(application:openFiles:),
open_files as extern "C" fn(&Object, Sel, id, id),
);
decl.register()
}
}
unsafe fn get_app(object: &Object) -> &mut App {
let wrapper_ptr: *mut c_void = *object.get_ivar(RUST_WRAPPER_IVAR_NAME);
&mut *(wrapper_ptr as *mut App)
}
extern "C" fn send_event(this: &Object, _sel: Sel, native_event: id) {
let event = unsafe { Event::from_native(native_event, None) };
if let Some(event) = event {
let app = unsafe { get_app(this) };
if let Some(callback) = app.event_callback.as_mut() {
if callback(event) {
return;
}
let app: id = msg_send![class!(NSApplication), sharedApplication];
let _: () = msg_send![app, activateIgnoringOtherApps: ignoring_other_apps.to_objc()];
}
}
unsafe {
let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
}
}
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
let app = unsafe { get_app(this) };
if let Some(callback) = app.finish_launching_callback.take() {
callback();
}
}
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
let app = unsafe { get_app(this) };
if let Some(callback) = app.become_active_callback.as_mut() {
callback();
}
}
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
let app = unsafe { get_app(this) };
if let Some(callback) = app.resign_active_callback.as_mut() {
callback();
}
}
extern "C" fn open_files(this: &Object, _: Sel, _: id, paths: id) {
let paths = unsafe {
(0..paths.count())
.into_iter()
.filter_map(|i| {
let path = paths.objectAtIndex(i);
match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
Ok(string) => Some(PathBuf::from(string)),
Err(err) => {
log::error!("error converting path to string: {}", err);
None
}
}
})
.collect::<Vec<_>>()
};
let app = unsafe { get_app(this) };
if let Some(callback) = app.open_files_callback.as_mut() {
callback(paths);
fn open_window(
&self,
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Rc<dyn platform::Window>> {
Ok(Rc::new(Window::open(options, executor)?))
}
}
+1
View File
@@ -0,0 +1 @@
#include <dispatch/dispatch.h>
+43
View File
@@ -0,0 +1,43 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
use async_task::Runnable;
use objc::{
class, msg_send,
runtime::{BOOL, YES},
sel, sel_impl,
};
use std::ffi::c_void;
use crate::platform;
include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
pub fn dispatch_get_main_queue() -> dispatch_queue_t {
unsafe { &_dispatch_main_q as *const _ as dispatch_queue_t }
}
pub struct Dispatcher;
impl platform::Dispatcher for Dispatcher {
fn is_main_thread(&self) -> bool {
let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
is_main_thread == YES
}
fn run_on_main_thread(&self, runnable: Runnable) {
unsafe {
dispatch_async_f(
dispatch_get_main_queue(),
runnable.into_raw() as *mut c_void,
Some(trampoline),
);
}
extern "C" fn trampoline(runnable: *mut c_void) {
let task = unsafe { Runnable::from_raw(runnable as *mut ()) };
task.run();
}
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
use super::Event;
use crate::{geometry::vector::vec2f, keymap::Keystroke};
use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event};
use cocoa::appkit::{
NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
+26
View File
@@ -0,0 +1,26 @@
use cocoa::foundation::{NSPoint, NSRect, NSSize};
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
pub trait Vector2FExt {
fn to_ns_point(&self) -> NSPoint;
fn to_ns_size(&self) -> NSSize;
}
pub trait RectFExt {
fn to_ns_rect(&self) -> NSRect;
}
impl Vector2FExt for Vector2F {
fn to_ns_point(&self) -> NSPoint {
NSPoint::new(self.x() as f64, self.y() as f64)
}
fn to_ns_size(&self) -> NSSize {
NSSize::new(self.x() as f64, self.y() as f64)
}
}
impl RectFExt for RectF {
fn to_ns_rect(&self) -> NSRect {
NSRect::new(self.origin().to_ns_point(), self.size().to_ns_size())
}
}
+30 -4
View File
@@ -1,8 +1,34 @@
use super::*;
mod app;
mod dispatcher;
mod event;
mod geometry;
mod runner;
mod window;
pub fn app() -> impl App {
app::App::default()
use crate::platform;
pub use app::App;
use cocoa::base::{BOOL, NO, YES};
pub use dispatcher::Dispatcher;
pub use runner::Runner;
use window::Window;
pub fn app() -> impl platform::App {
App::new()
}
pub fn runner() -> impl platform::Runner {
Runner::new()
}
trait BoolExt {
fn to_objc(self) -> BOOL;
}
impl BoolExt for bool {
fn to_objc(self) -> BOOL {
if self {
YES
} else {
NO
}
}
}
+188
View File
@@ -0,0 +1,188 @@
use crate::platform::Event;
use cocoa::{
appkit::NSApplicationActivationPolicy::NSApplicationActivationPolicyRegular,
base::{id, nil},
foundation::{NSArray, NSAutoreleasePool, NSString},
};
use ctor::ctor;
use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use std::{
ffi::CStr,
os::raw::{c_char, c_void},
path::PathBuf,
ptr,
};
const RUNNER_IVAR: &'static str = "runner";
static mut APP_CLASS: *const Class = ptr::null();
static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
#[ctor]
unsafe fn build_classes() {
APP_CLASS = {
let mut decl = ClassDecl::new("GPUIApplication", class!(NSApplication)).unwrap();
decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
decl.add_method(
sel!(sendEvent:),
send_event as extern "C" fn(&mut Object, Sel, id),
);
decl.register()
};
APP_DELEGATE_CLASS = {
let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
decl.add_ivar::<*mut c_void>(RUNNER_IVAR);
decl.add_method(
sel!(applicationDidFinishLaunching:),
did_finish_launching as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(applicationDidBecomeActive:),
did_become_active as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(applicationDidResignActive:),
did_resign_active as extern "C" fn(&mut Object, Sel, id),
);
decl.add_method(
sel!(application:openFiles:),
open_files as extern "C" fn(&mut Object, Sel, id, id),
);
decl.register()
}
}
#[derive(Default)]
pub struct Runner {
finish_launching_callback: Option<Box<dyn FnOnce()>>,
become_active_callback: Option<Box<dyn FnMut()>>,
resign_active_callback: Option<Box<dyn FnMut()>>,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
open_files_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
}
impl Runner {
pub fn new() -> Self {
Default::default()
}
}
impl crate::platform::Runner for Runner {
fn on_finish_launching<F: 'static + FnOnce()>(mut self, callback: F) -> Self {
self.finish_launching_callback = Some(Box::new(callback));
self
}
fn on_become_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
log::info!("become active");
self.become_active_callback = Some(Box::new(callback));
self
}
fn on_resign_active<F: 'static + FnMut()>(mut self, callback: F) -> Self {
self.resign_active_callback = Some(Box::new(callback));
self
}
fn on_event<F: 'static + FnMut(Event) -> bool>(mut self, callback: F) -> Self {
self.event_callback = Some(Box::new(callback));
self
}
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(mut self, callback: F) -> Self {
self.open_files_callback = Some(Box::new(callback));
self
}
fn run(self) {
unsafe {
let self_ptr = Box::into_raw(Box::new(self));
let pool = NSAutoreleasePool::new(nil);
let app: id = msg_send![APP_CLASS, sharedApplication];
let _: () = msg_send![
app,
setActivationPolicy: NSApplicationActivationPolicyRegular
];
(*app).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
let app_delegate: id = msg_send![APP_DELEGATE_CLASS, new];
(*app_delegate).set_ivar(RUNNER_IVAR, self_ptr as *mut c_void);
let _: () = msg_send![app, setDelegate: app_delegate];
let _: () = msg_send![app, run];
let _: () = msg_send![pool, drain];
// The Runner is done running when we get here, so we can reinstantiate the Box and drop it.
Box::from_raw(self_ptr);
}
}
}
unsafe fn get_runner(object: &mut Object) -> &mut Runner {
let runner_ptr: *mut c_void = *object.get_ivar(RUNNER_IVAR);
&mut *(runner_ptr as *mut Runner)
}
extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) {
let event = unsafe { Event::from_native(native_event, None) };
if let Some(event) = event {
let runner = unsafe { get_runner(this) };
if let Some(callback) = runner.event_callback.as_mut() {
if callback(event) {
return;
}
}
}
unsafe {
let _: () = msg_send![super(this, class!(NSApplication)), sendEvent: native_event];
}
}
extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
let runner = unsafe { get_runner(this) };
if let Some(callback) = runner.finish_launching_callback.take() {
callback();
}
}
extern "C" fn did_become_active(this: &mut Object, _: Sel, _: id) {
let runner = unsafe { get_runner(this) };
if let Some(callback) = runner.become_active_callback.as_mut() {
callback();
}
}
extern "C" fn did_resign_active(this: &mut Object, _: Sel, _: id) {
let runner = unsafe { get_runner(this) };
if let Some(callback) = runner.resign_active_callback.as_mut() {
callback();
}
}
extern "C" fn open_files(this: &mut Object, _: Sel, _: id, paths: id) {
let paths = unsafe {
(0..paths.count())
.into_iter()
.filter_map(|i| {
let path = paths.objectAtIndex(i);
match CStr::from_ptr(path.UTF8String() as *mut c_char).to_str() {
Ok(string) => Some(PathBuf::from(string)),
Err(err) => {
log::error!("error converting path to string: {}", err);
None
}
}
})
.collect::<Vec<_>>()
};
let runner = unsafe { get_runner(this) };
if let Some(callback) = runner.open_files_callback.as_mut() {
callback(paths);
}
}
+353
View File
@@ -0,0 +1,353 @@
use crate::{
executor,
geometry::vector::Vector2F,
platform::{self, Event},
};
use anyhow::{anyhow, Result};
use cocoa::{
appkit::{
NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable,
NSWindow, NSWindowStyleMask,
},
base::{id, nil},
foundation::{NSAutoreleasePool, NSSize, NSString},
};
use ctor::ctor;
use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Class, Object, Sel, BOOL, NO, YES},
sel, sel_impl,
};
use smol::Timer;
use std::{
cell::{Cell, RefCell},
ffi::c_void,
mem, ptr,
rc::Rc,
time::{Duration, Instant},
};
use super::geometry::RectFExt;
const WINDOW_STATE_IVAR: &'static str = "windowState";
static mut WINDOW_CLASS: *const Class = ptr::null();
static mut VIEW_CLASS: *const Class = ptr::null();
static mut DELEGATE_CLASS: *const Class = ptr::null();
#[ctor]
unsafe fn build_classes() {
WINDOW_CLASS = {
let mut decl = ClassDecl::new("GPUIWindow", class!(NSWindow)).unwrap();
decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
decl.add_method(sel!(dealloc), dealloc_window as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(canBecomeMainWindow),
yes as extern "C" fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(canBecomeKeyWindow),
yes as extern "C" fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(sendEvent:),
send_event as extern "C" fn(&Object, Sel, id),
);
decl.register()
};
VIEW_CLASS = {
let mut decl = ClassDecl::new("GPUIView", class!(NSView)).unwrap();
decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
decl.add_method(
sel!(keyDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDown:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseUp:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(mouseDragged:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.add_method(
sel!(scrollWheel:),
handle_view_event as extern "C" fn(&Object, Sel, id),
);
decl.register()
};
DELEGATE_CLASS = {
let mut decl = ClassDecl::new("GPUIWindowDelegate", class!(NSObject)).unwrap();
decl.add_method(
sel!(dealloc),
dealloc_delegate as extern "C" fn(&Object, Sel),
);
decl.add_ivar::<*mut c_void>(WINDOW_STATE_IVAR);
decl.add_method(
sel!(windowDidResize:),
window_did_resize as extern "C" fn(&Object, Sel, id),
);
decl.register()
};
}
pub struct Window(Rc<WindowState>);
struct WindowState {
native_window: id,
event_callback: RefCell<Option<Box<dyn FnMut(Event) -> bool>>>,
resize_callback: RefCell<Option<Box<dyn FnMut(NSSize, f64)>>>,
synthetic_drag_counter: Cell<usize>,
executor: Rc<executor::Foreground>,
}
impl Window {
pub fn open(
options: platform::WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Self> {
unsafe {
let pool = NSAutoreleasePool::new(nil);
let frame = options.bounds.to_ns_rect();
let style_mask = NSWindowStyleMask::NSClosableWindowMask
| NSWindowStyleMask::NSMiniaturizableWindowMask
| NSWindowStyleMask::NSResizableWindowMask
| NSWindowStyleMask::NSTitledWindowMask;
let native_window: id = msg_send![WINDOW_CLASS, alloc];
let native_window = native_window.initWithContentRect_styleMask_backing_defer_(
frame,
style_mask,
NSBackingStoreBuffered,
NO,
);
if native_window == nil {
return Err(anyhow!("window returned nil from initializer"));
}
let delegate: id = msg_send![DELEGATE_CLASS, alloc];
let delegate = delegate.init();
if native_window == nil {
return Err(anyhow!("delegate returned nil from initializer"));
}
native_window.setDelegate_(delegate);
let native_view: id = msg_send![VIEW_CLASS, alloc];
let native_view = NSView::init(native_view);
if native_view == nil {
return Err(anyhow!("view return nil from initializer"));
}
let window = Self(Rc::new(WindowState {
native_window,
event_callback: RefCell::new(None),
resize_callback: RefCell::new(None),
synthetic_drag_counter: Cell::new(0),
executor,
}));
(*native_window).set_ivar(
WINDOW_STATE_IVAR,
Rc::into_raw(window.0.clone()) as *const c_void,
);
(*native_view).set_ivar(
WINDOW_STATE_IVAR,
Rc::into_raw(window.0.clone()) as *const c_void,
);
(*delegate).set_ivar(
WINDOW_STATE_IVAR,
Rc::into_raw(window.0.clone()) as *const c_void,
);
if let Some(title) = options.title.as_ref() {
native_window.setTitle_(NSString::alloc(nil).init_str(title));
}
native_window.setAcceptsMouseMovedEvents_(YES);
native_view.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
native_view.setWantsBestResolutionOpenGLSurface_(YES);
// From winit crate: On Mojave, views automatically become layer-backed shortly after
// being added to a native_window. Changing the layer-backedness of a view breaks the
// association between the view and its associated OpenGL context. To work around this,
// on we explicitly make the view layer-backed up front so that AppKit doesn't do it
// itself and break the association with its context.
native_view.setWantsLayer(YES);
native_view.layer().setBackgroundColor_(
msg_send![class!(NSColor), colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0],
);
native_window.setContentView_(native_view.autorelease());
native_window.makeFirstResponder_(native_view);
native_window.center();
native_window.makeKeyAndOrderFront_(nil);
pool.drain();
Ok(window)
}
}
pub fn zoom(&self) {
unsafe {
self.0.native_window.performZoom_(nil);
}
}
pub fn size(&self) -> NSSize {
self.0.size()
}
pub fn backing_scale_factor(&self) -> f64 {
self.0.backing_scale_factor()
}
pub fn on_event<F: 'static + FnMut(Event) -> bool>(&mut self, callback: F) {
*self.0.event_callback.borrow_mut() = Some(Box::new(callback));
}
pub fn on_resize<F: 'static + FnMut(NSSize, f64)>(&mut self, callback: F) {
*self.0.resize_callback.borrow_mut() = Some(Box::new(callback));
}
}
impl Drop for Window {
fn drop(&mut self) {
unsafe {
self.0.native_window.close();
let _: () = msg_send![self.0.native_window.delegate(), release];
}
}
}
impl platform::Window for Window {}
impl WindowState {
fn size(&self) -> NSSize {
let view_frame = unsafe { NSView::frame(self.native_window.contentView()) };
view_frame.size
}
fn backing_scale_factor(&self) -> f64 {
unsafe {
let screen: id = msg_send![self.native_window, screen];
NSScreen::backingScaleFactor(screen)
}
}
fn next_synthetic_drag_id(&self) -> usize {
let next_id = self.synthetic_drag_counter.get() + 1;
self.synthetic_drag_counter.set(next_id);
next_id
}
}
unsafe fn window_state(object: &Object) -> Rc<WindowState> {
let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
let rc1 = Rc::from_raw(raw as *mut WindowState);
let rc2 = rc1.clone();
mem::forget(rc1);
rc2
}
unsafe fn drop_window_state(object: &Object) {
let raw: *mut c_void = *object.get_ivar(WINDOW_STATE_IVAR);
Rc::from_raw(raw as *mut WindowState);
}
extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
YES
}
extern "C" fn dealloc_window(this: &Object, _: Sel) {
unsafe {
drop_window_state(this);
let () = msg_send![super(this, class!(NSWindow)), dealloc];
}
}
extern "C" fn dealloc_view(this: &Object, _: Sel) {
unsafe {
drop_window_state(this);
let () = msg_send![super(this, class!(NSView)), dealloc];
}
}
extern "C" fn dealloc_delegate(this: &Object, _: Sel) {
unsafe {
let raw: *mut c_void = *this.get_ivar(WINDOW_STATE_IVAR);
Rc::from_raw(raw as *mut WindowState);
let () = msg_send![super(this, class!(NSObject)), dealloc];
}
}
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
let window = unsafe { window_state(this) };
let event = unsafe { Event::from_native(native_event, Some(window.size().height as f32)) };
if let Some(event) = event {
match event {
Event::LeftMouseDragged { position } => schedule_synthetic_drag(&window, position),
Event::LeftMouseUp { .. } => {
window.next_synthetic_drag_id();
}
_ => {}
}
if let Some(callback) = window.event_callback.borrow_mut().as_mut() {
if callback(event) {
return;
}
}
}
}
extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
unsafe {
let () = msg_send![super(this, class!(NSWindow)), sendEvent: native_event];
}
}
extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
let window = unsafe { window_state(this) };
let size = window.size();
let scale_factor = window.backing_scale_factor();
if let Some(callback) = window.resize_callback.borrow_mut().as_mut() {
callback(size, scale_factor);
}
drop(window);
}
fn schedule_synthetic_drag(window_state: &Rc<WindowState>, position: Vector2F) {
let drag_id = window_state.next_synthetic_drag_id();
let weak_window_state = Rc::downgrade(window_state);
let instant = Instant::now() + Duration::from_millis(16);
window_state
.executor
.spawn(async move {
Timer::at(instant).await;
if let Some(window_state) = weak_window_state.upgrade() {
if window_state.synthetic_drag_counter.get() == drag_id {
if let Some(callback) = window_state.event_callback.borrow_mut().as_mut() {
schedule_synthetic_drag(&window_state, position);
callback(Event::LeftMouseDragged { position });
}
}
}
})
.detach();
}
+27 -3
View File
@@ -6,11 +6,13 @@ pub mod current {
pub use super::mac::*;
}
use std::path::PathBuf;
use crate::{executor, geometry::rect::RectF};
use anyhow::Result;
use async_task::Runnable;
use event::Event;
use std::{path::PathBuf, rc::Rc, sync::Arc};
pub trait App {
pub trait Runner {
fn on_finish_launching<F: 'static + FnOnce()>(self, callback: F) -> Self where;
fn on_become_active<F: 'static + FnMut()>(self, callback: F) -> Self;
fn on_resign_active<F: 'static + FnMut()>(self, callback: F) -> Self;
@@ -18,3 +20,25 @@ pub trait App {
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
fn run(self);
}
pub trait App {
fn dispatcher(&self) -> Arc<dyn Dispatcher>;
fn activate(&self, ignoring_other_apps: bool);
fn open_window(
&self,
options: WindowOptions,
executor: Rc<executor::Foreground>,
) -> Result<Rc<dyn Window>>;
}
pub trait Dispatcher: Send + Sync {
fn is_main_thread(&self) -> bool;
fn run_on_main_thread(&self, task: Runnable);
}
pub trait Window {}
pub struct WindowOptions<'a> {
pub bounds: RectF,
pub title: Option<&'a str>,
}