Start rebuilding with a cleanly-separated UI framework

This commit is contained in:
Nathan Sobo
2021-02-20 10:02:34 -07:00
commit b400449a58
13 changed files with 1863 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
use crate::{geometry::vector::Vector2F, keymap::Keystroke};
#[derive(Debug)]
pub enum Event {
KeyDown {
keystroke: Keystroke,
chars: String,
},
ScrollWheel {
position: Vector2F,
delta: Vector2F,
precise: bool,
},
LeftMouseDown {
position: Vector2F,
cmd: bool,
},
LeftMouseUp {
position: Vector2F,
},
LeftMouseDragged {
position: Vector2F,
},
}
+176
View File
@@ -0,0 +1,176 @@
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,
};
#[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>)>>,
}
const RUST_WRAPPER_IVAR_NAME: &'static str = "rustWrapper";
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
}
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) {
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;
}
}
}
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);
}
}
+115
View File
@@ -0,0 +1,115 @@
use super::Event;
use crate::{geometry::vector::vec2f, keymap::Keystroke};
use cocoa::appkit::{
NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY,
NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY,
NSPageUpFunctionKey as PAGE_UP_KEY, NSRightArrowFunctionKey as ARROW_RIGHT_KEY,
NSUpArrowFunctionKey as ARROW_UP_KEY,
};
use cocoa::{
appkit::{NSEvent as _, NSEventModifierFlags, NSEventType},
base::{id, YES},
foundation::NSString as _,
};
use std::{ffi::CStr, os::raw::c_char};
const BACKSPACE_KEY: u16 = 0x7f;
const ENTER_KEY: u16 = 0x0d;
const ESCAPE_KEY: u16 = 0x1b;
impl Event {
pub unsafe fn from_native(native_event: id, window_height: Option<f32>) -> Option<Self> {
let event_type = native_event.eventType();
// Filter out event types that aren't in the NSEventType enum.
// See https://github.com/servo/cocoa-rs/issues/155#issuecomment-323482792 for details.
match event_type as u64 {
0 | 21 | 32 | 33 | 35 | 36 | 37 => {
return None;
}
_ => {}
}
match event_type {
NSEventType::NSKeyDown => {
let modifiers = native_event.modifierFlags();
let unmodified_chars = native_event.charactersIgnoringModifiers();
let unmodified_chars = CStr::from_ptr(unmodified_chars.UTF8String() as *mut c_char)
.to_str()
.unwrap();
let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() {
match first_char as u16 {
ARROW_UP_KEY => "up",
ARROW_DOWN_KEY => "down",
ARROW_LEFT_KEY => "left",
ARROW_RIGHT_KEY => "right",
PAGE_UP_KEY => "pageup",
PAGE_DOWN_KEY => "pagedown",
BACKSPACE_KEY => "backspace",
ENTER_KEY => "enter",
DELETE_KEY => "delete",
ESCAPE_KEY => "escape",
_ => unmodified_chars,
}
} else {
return None;
};
let chars = native_event.characters();
let chars = CStr::from_ptr(chars.UTF8String() as *mut c_char)
.to_str()
.unwrap()
.into();
Some(Self::KeyDown {
keystroke: Keystroke {
ctrl: modifiers.contains(NSEventModifierFlags::NSControlKeyMask),
alt: modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask),
shift: modifiers.contains(NSEventModifierFlags::NSShiftKeyMask),
cmd: modifiers.contains(NSEventModifierFlags::NSCommandKeyMask),
key: unmodified_chars.into(),
},
chars,
})
}
NSEventType::NSLeftMouseDown => {
window_height.map(|window_height| Self::LeftMouseDown {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
cmd: native_event
.modifierFlags()
.contains(NSEventModifierFlags::NSCommandKeyMask),
})
}
NSEventType::NSLeftMouseUp => window_height.map(|window_height| Self::LeftMouseUp {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
}),
NSEventType::NSLeftMouseDragged => {
window_height.map(|window_height| Self::LeftMouseDragged {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
})
}
NSEventType::NSScrollWheel => window_height.map(|window_height| Self::ScrollWheel {
position: vec2f(
native_event.locationInWindow().x as f32,
window_height - native_event.locationInWindow().y as f32,
),
delta: vec2f(
native_event.scrollingDeltaX() as f32,
native_event.scrollingDeltaY() as f32,
),
precise: native_event.hasPreciseScrollingDeltas() == YES,
}),
_ => None,
}
}
}
+8
View File
@@ -0,0 +1,8 @@
use super::*;
mod app;
mod event;
pub fn app() -> impl App {
app::App::default()
}
+20
View File
@@ -0,0 +1,20 @@
mod event;
#[cfg(target_os = "macos")]
pub mod mac;
pub mod current {
#[cfg(target_os = "macos")]
pub use super::mac::*;
}
use std::path::PathBuf;
use event::Event;
pub trait App {
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;
fn on_event<F: 'static + FnMut(Event) -> bool>(self, callback: F) -> Self;
fn on_open_files<F: 'static + FnMut(Vec<PathBuf>)>(self, callback: F) -> Self;
fn run(self);
}