Devised a better (safer) way to handle UI/Controller/Delegate setup
This commit is contained in:
parent
80ba209413
commit
f49eff24f9
|
@ -17,14 +17,15 @@ use crate::menu::Menu;
|
|||
mod events;
|
||||
use events::register_app_class;
|
||||
|
||||
|
||||
pub trait AppDelegate {
|
||||
pub trait Dispatcher {
|
||||
type Message: Send + Sync;
|
||||
|
||||
fn on_message(&self, _message: Self::Message) {}
|
||||
}
|
||||
|
||||
pub trait AppDelegate {
|
||||
fn did_finish_launching(&self) {}
|
||||
fn did_become_active(&self) {}
|
||||
|
||||
fn on_message(&self, _message: Self::Message) {}
|
||||
}
|
||||
|
||||
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
|
||||
|
@ -60,7 +61,7 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate<Message = M> {
|
||||
impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher<Message = M> {
|
||||
/// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate,
|
||||
/// and passing back through there. All messages are currently dispatched on the main thread.
|
||||
pub fn dispatch(message: M) {
|
||||
|
|
|
@ -8,3 +8,4 @@ pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr";
|
|||
pub(crate) static WEBVIEW_CONFIG_VAR: &str = "rstWebViewConfig";
|
||||
pub(crate) static WEBVIEW_VAR: &str = "rstWebView";
|
||||
pub(crate) static WEBVIEW_CONTROLLER_PTR: &str = "rstWebViewControllerPtr";
|
||||
pub(crate) static WINDOW_CONTROLLER_PTR: &str = "rstWindowController";
|
||||
|
|
|
@ -45,7 +45,7 @@ pub mod window;
|
|||
pub use url;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::app::{App, AppDelegate};
|
||||
pub use crate::app::{App, AppDelegate, Dispatcher};
|
||||
|
||||
pub use crate::menu::{Menu, MenuItem};
|
||||
pub use crate::notifications::{Notification, NotificationCenter, NotificationAuthOption};
|
||||
|
@ -54,14 +54,14 @@ pub mod prelude {
|
|||
pub use crate::networking::URLRequest;
|
||||
|
||||
pub use crate::window::{
|
||||
Window, WindowWrapper as WinWrapper, WindowController
|
||||
Window, WindowController, WindowHandle
|
||||
};
|
||||
|
||||
pub use crate::webview::{
|
||||
WebView, WebViewConfig, WebViewController
|
||||
};
|
||||
|
||||
pub use crate::view::{View, ViewController, ViewWrapper};
|
||||
pub use crate::view::{View, ViewHandle, ViewController};
|
||||
|
||||
pub use appkit_derive::{
|
||||
WindowWrapper, ViewWrapper
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
//! for in the modern era. It also implements a few helpers for things like setting a background
|
||||
//! color, and enforcing layer backing by default.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Once;
|
||||
|
||||
use cocoa::base::{id, nil, YES, NO};
|
||||
|
@ -42,10 +44,19 @@ extern fn update_layer(this: &Object, _: Sel) {
|
|||
extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: id) -> NSUInteger {
|
||||
unsafe {
|
||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||
let view = ptr as *const T;
|
||||
(*view).dragging_entered(DragInfo {
|
||||
let view_ptr = ptr as *const RefCell<T>;
|
||||
let view = Rc::from_raw(view_ptr);
|
||||
|
||||
let response = {
|
||||
let v = view.borrow();
|
||||
|
||||
(*v).dragging_entered(DragInfo {
|
||||
info: Id::from_ptr(info)
|
||||
}).into()
|
||||
};
|
||||
|
||||
Rc::into_raw(view);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,14 +64,22 @@ extern fn dragging_entered<T: ViewController>(this: &mut Object, _: Sel, info: i
|
|||
extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||
unsafe {
|
||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||
let view = ptr as *const T;
|
||||
let view_ptr = ptr as *const RefCell<T>;
|
||||
let view = Rc::from_raw(view_ptr);
|
||||
|
||||
match (*view).prepare_for_drag_operation(DragInfo {
|
||||
let response = {
|
||||
let v = view.borrow();
|
||||
|
||||
match (*v).prepare_for_drag_operation(DragInfo {
|
||||
info: Id::from_ptr(info)
|
||||
}) {
|
||||
true => YES,
|
||||
false => NO
|
||||
}
|
||||
};
|
||||
|
||||
Rc::into_raw(view);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,14 +87,22 @@ extern fn prepare_for_drag_operation<T: ViewController>(this: &mut Object, _: Se
|
|||
extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) -> BOOL {
|
||||
unsafe {
|
||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||
let view = ptr as *const T;
|
||||
let view_ptr = ptr as *const RefCell<T>;
|
||||
let view = Rc::from_raw(view_ptr);
|
||||
|
||||
match (*view).perform_drag_operation(DragInfo {
|
||||
let response = {
|
||||
let v = view.borrow();
|
||||
|
||||
match (*v).perform_drag_operation(DragInfo {
|
||||
info: Id::from_ptr(info)
|
||||
}) {
|
||||
true => YES,
|
||||
false => NO
|
||||
}
|
||||
};
|
||||
|
||||
Rc::into_raw(view);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,11 +110,18 @@ extern fn perform_drag_operation<T: ViewController>(this: &mut Object, _: Sel, i
|
|||
extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
||||
unsafe {
|
||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||
let view = ptr as *const T;
|
||||
let view_ptr = ptr as *const RefCell<T>;
|
||||
let view = Rc::from_raw(view_ptr);
|
||||
|
||||
(*view).conclude_drag_operation(DragInfo {
|
||||
let response = {
|
||||
let v = view.borrow();
|
||||
(*v).conclude_drag_operation(DragInfo {
|
||||
info: Id::from_ptr(info)
|
||||
});
|
||||
};
|
||||
|
||||
Rc::into_raw(view);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +129,18 @@ extern fn conclude_drag_operation<T: ViewController>(this: &mut Object, _: Sel,
|
|||
extern fn dragging_exited<T: ViewController>(this: &mut Object, _: Sel, info: id) {
|
||||
unsafe {
|
||||
let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR);
|
||||
let view = ptr as *const T;
|
||||
(*view).dragging_exited(DragInfo {
|
||||
let view_ptr = ptr as *const RefCell<T>;
|
||||
let view = Rc::from_raw(view_ptr);
|
||||
|
||||
let response = {
|
||||
let v = view.borrow();
|
||||
(*v).dragging_exited(DragInfo {
|
||||
info: Id::from_ptr(info)
|
||||
});
|
||||
};
|
||||
|
||||
Rc::into_raw(view);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,4 +6,4 @@ pub mod traits;
|
|||
pub use traits::*;
|
||||
|
||||
pub mod view;
|
||||
pub use view::View;
|
||||
pub use view::{View, ViewHandle};
|
||||
|
|
|
@ -5,13 +5,20 @@ use objc::runtime::Object;
|
|||
use objc_id::ShareId;
|
||||
|
||||
use crate::dragdrop::{DragInfo, DragOperation};
|
||||
use crate::view::ViewHandle;
|
||||
|
||||
pub trait ViewWrapper {
|
||||
fn get_handle(&self) -> Option<ShareId<Object>>;
|
||||
pub trait Node {
|
||||
fn get_backing_node(&self) -> Option<ShareId<Object>>;
|
||||
}
|
||||
|
||||
pub trait ViewController {
|
||||
fn did_load(&self);
|
||||
/// Where possible, we try to respect the lazy-ish-loading of macOS/iOS systems. This hook
|
||||
/// notifies you when the `View` has actually loaded into memory, and you're free to do things
|
||||
/// with it.
|
||||
///
|
||||
/// Note that you can trigger the view to load earlier, if need be... and in many cases it's
|
||||
/// fine. This is a platform-specific detail you may want to read up on. :)
|
||||
fn did_load(&mut self, _view: ViewHandle) {}
|
||||
|
||||
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform.
|
||||
fn dragging_entered(&self, _info: DragInfo) -> DragOperation { DragOperation::None }
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
//! A wrapper for `NSViewController`. Uses interior mutability to
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use cocoa::base::{id, nil, YES};
|
||||
use cocoa::foundation::NSArray;
|
||||
|
@ -13,29 +14,30 @@ use objc::{msg_send, sel, sel_impl};
|
|||
use crate::color::Color;
|
||||
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
|
||||
use crate::pasteboard::PasteboardType;
|
||||
use crate::view::traits::ViewController;
|
||||
use crate::view::traits::{Node, ViewController};
|
||||
use crate::view::controller::register_controller_class;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ViewInner {
|
||||
pub controller: Option<ShareId<Object>>
|
||||
}
|
||||
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
|
||||
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
|
||||
/// side anyway.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ViewHandle(Option<ShareId<Object>>);
|
||||
|
||||
impl ViewInner {
|
||||
pub fn configure<T: ViewController + 'static>(&mut self, controller: &T) {
|
||||
self.controller = Some(unsafe {
|
||||
let view_controller: id = msg_send![register_controller_class::<T>(), new];
|
||||
(&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, controller as *const T as usize);
|
||||
|
||||
let view: id = msg_send![view_controller, view];
|
||||
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, controller as *const T as usize);
|
||||
|
||||
ShareId::from_ptr(view_controller)
|
||||
});
|
||||
impl ViewHandle {
|
||||
/// Call this to set the background color for the backing layer.
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let view: id = msg_send![*controller, view];
|
||||
(*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color());
|
||||
let _: () = msg_send![view, setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Register this view for drag and drop operations.
|
||||
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
||||
if let Some(controller) = &self.controller {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let types = NSArray::arrayWithObjects(nil, &types.iter().map(|t| {
|
||||
t.to_nsstring()
|
||||
|
@ -46,49 +48,78 @@ impl ViewInner {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let view: id = msg_send![*controller, view];
|
||||
(*view).set_ivar(BACKGROUND_COLOR, color.into_platform_specific_color());
|
||||
let _: () = msg_send![view, setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct View(Rc<RefCell<ViewInner>>);
|
||||
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards
|
||||
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
|
||||
/// of Cocoa is essentially Heap'd, so... well, enjoy.
|
||||
#[derive(Clone)]
|
||||
pub struct View<T> {
|
||||
internal_callback_ptr: *const RefCell<T>,
|
||||
pub objc_controller: ViewHandle,
|
||||
pub controller: Rc<RefCell<T>>
|
||||
}
|
||||
|
||||
impl<T> View<T> where T: ViewController + 'static {
|
||||
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over
|
||||
/// to your supplied view controller.
|
||||
pub fn new(controller: T) -> Self {
|
||||
let controller = Rc::new(RefCell::new(controller));
|
||||
|
||||
let internal_callback_ptr = {
|
||||
let cloned = Rc::clone(&controller);
|
||||
Rc::into_raw(cloned)
|
||||
};
|
||||
|
||||
let inner = unsafe {
|
||||
let view_controller: id = msg_send![register_controller_class::<T>(), new];
|
||||
(&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
|
||||
|
||||
let view: id = msg_send![view_controller, view];
|
||||
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
|
||||
|
||||
ShareId::from_ptr(view_controller)
|
||||
};
|
||||
|
||||
impl View {
|
||||
pub fn configure<T: ViewController + 'static>(&self, controller: &T) {
|
||||
{
|
||||
let mut view = self.0.borrow_mut();
|
||||
view.configure(controller);
|
||||
let mut vc = controller.borrow_mut();
|
||||
(*vc).did_load(ViewHandle(Some(inner.clone())));
|
||||
}
|
||||
|
||||
controller.did_load();
|
||||
View {
|
||||
internal_callback_ptr: internal_callback_ptr,
|
||||
objc_controller: ViewHandle(Some(inner)),
|
||||
controller: controller
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_handle(&self) -> Option<ShareId<Object>> {
|
||||
let view = self.0.borrow();
|
||||
view.controller.clone()
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
self.objc_controller.set_background_color(color);
|
||||
}
|
||||
|
||||
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
|
||||
let view = self.0.borrow();
|
||||
view.register_for_dragged_types(types);
|
||||
}
|
||||
|
||||
pub fn set_background_color(&self, color: Color) {
|
||||
let view = self.0.borrow();
|
||||
view.set_background_color(color);
|
||||
self.objc_controller.register_for_dragged_types(types);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for View {
|
||||
impl<T> Node for View<T> {
|
||||
/// Returns the Objective-C object used for handling the view heirarchy.
|
||||
fn get_backing_node(&self) -> Option<ShareId<Object>> {
|
||||
self.objc_controller.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::fmt::Debug for View<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "View ({:p})", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for View<T> {
|
||||
/// A bit of extra cleanup for delegate callback pointers.
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = Rc::from_raw(self.internal_callback_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ impl WebView {
|
|||
webview.configure(controller);
|
||||
}
|
||||
|
||||
controller.did_load();
|
||||
//controller.did_load();
|
||||
}
|
||||
|
||||
pub fn get_handle(&self) -> Option<ShareId<Object>> {
|
||||
|
|
147
appkit/src/window/handle.rs
Normal file
147
appkit/src/window/handle.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
//! Implements `WindowHandle`, which wraps a lower-level `NSWindowController` and handles method
|
||||
//! shuffling to call through to the window it holds.
|
||||
//!
|
||||
//! We use `NSWindowController` as it has lifecycle methods that are useful, in addition to the
|
||||
//! standard `NSWindowDelegate` methods.
|
||||
|
||||
use cocoa::base::{id, nil, YES, NO};
|
||||
use cocoa::foundation::{NSSize, NSString};
|
||||
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::view::traits::Node;
|
||||
|
||||
//use crate::toolbar::traits::ToolbarDelegate;
|
||||
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct WindowHandle(pub Option<ShareId<Object>>);
|
||||
|
||||
impl WindowHandle {
|
||||
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
|
||||
/// to the Objective C runtime.
|
||||
pub fn set_title(&self, title: &str) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitle:title];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the title visibility for the underlying window.
|
||||
pub fn set_title_visibility(&self, visibility: usize) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitleVisibility:visibility];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for configuring whether the window is movable via the background.
|
||||
pub fn set_movable_by_background(&self, movable: bool) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setMovableByWindowBackground:match movable {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting whether this titlebar appears transparent.
|
||||
pub fn set_titlebar_appears_transparent(&self, transparent: bool) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitlebarAppearsTransparent:match transparent {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting this Window autosave name.
|
||||
pub fn set_autosave_name(&mut self, name: &str) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let autosave = NSString::alloc(nil).init_str(name);
|
||||
let _: () = msg_send![window, setFrameAutosaveName:autosave];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the minimum size this window can shrink to.
|
||||
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let size = NSSize::new(width.into(), height.into());
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setMinSize:size];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting a toolbar on this window. Note that this takes ownership of whatever
|
||||
/// `ToolbarDelegate` you pass! The underlying `NSToolbar` is a bit... old, and it's just
|
||||
/// easier to do things this way.
|
||||
///
|
||||
/// If you find yourself in a position where you need your toolbar after the fact, you
|
||||
/// probably have bigger issues.
|
||||
//pub fn set_toolbar<T: ToolbarDelegate + 'static>(&mut self, identifier: &str, toolbar: T) {
|
||||
/*let toolbar = Toolbar::new(identifier, toolbar);
|
||||
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setToolbar:&*toolbar.inner];
|
||||
}
|
||||
}
|
||||
|
||||
self.toolbar = Some(toolbar);*/
|
||||
//}
|
||||
|
||||
/// Used for setting the content view controller for this window.
|
||||
pub fn set_content_view_controller<T: Node + 'static>(&self, view_controller: &T) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
if let Some(vc) = view_controller.get_backing_node() {
|
||||
let _: () = msg_send![*controller, setContentViewController:&*vc];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the
|
||||
/// most common use case, hence why this method was chosen - if you want or need something
|
||||
/// else, feel free to open an issue to discuss.
|
||||
///
|
||||
/// You should never be calling this yourself, mind you - Alchemy core handles this for you.
|
||||
pub fn show(&self) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let _: () = msg_send![*controller, showWindow:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
|
||||
/// window.
|
||||
///
|
||||
/// I dunno what else to say here, lol.
|
||||
pub fn close(&self) {
|
||||
if let Some(controller) = &self.0 {
|
||||
unsafe {
|
||||
let _: () = msg_send![*controller, close];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
//! Implements wrappers and traits for `NSWindowController` and associated types.
|
||||
|
||||
pub mod traits;
|
||||
pub use traits::{WindowController, WindowWrapper};
|
||||
pub use traits::WindowController;
|
||||
|
||||
mod controller;
|
||||
|
||||
pub mod config;
|
||||
pub use config::{WindowConfig, WindowStyle};
|
||||
|
||||
pub mod handle;
|
||||
pub use handle::WindowHandle;
|
||||
|
||||
pub mod window;
|
||||
pub use window::{Window, WindowTitleVisibility};
|
||||
|
|
|
@ -2,59 +2,9 @@
|
|||
//! module. There's a few different ones, and it's just... cleaner, if
|
||||
//! it's organized here.
|
||||
|
||||
use crate::window::handle::WindowHandle;
|
||||
use crate::window::WindowConfig;
|
||||
|
||||
/// `WindowController` is a trait that handles providing higher level methods
|
||||
/// that map into platform specific methods. Typically, you won't want to (or at least, won't need
|
||||
/// to) implement this yourself - simply derive `WindowWrapper` and it'll work for you.
|
||||
///
|
||||
/// By deriving or implementing this, you get usable methods on your struct - for example:
|
||||
///
|
||||
/// ```
|
||||
/// use appkit::{AppDelegate, Window, WindowController, WindowWrapper};
|
||||
///
|
||||
/// #[derive(Default, WindowWrapper)]
|
||||
/// struct MyWindow {
|
||||
/// window: Window
|
||||
/// }
|
||||
///
|
||||
/// impl WindowController for MyWindow {
|
||||
/// // The default implementation is actually okay!
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default)]
|
||||
/// struct MyApp {
|
||||
/// window: MyWindow
|
||||
/// }
|
||||
///
|
||||
/// impl AppDelegate for MyApp {
|
||||
/// fn did_finish_launching(&mut self) {
|
||||
/// window.show();
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new("com.myapp.lol", MyApp::default());
|
||||
/// app.run();
|
||||
/// }
|
||||
/// ```
|
||||
pub trait WindowWrapper {
|
||||
/// Sets the title for the underlying window.
|
||||
fn set_title(&self, title: &str);
|
||||
|
||||
/// Calls through to the NSWindow show method (technically, `[NSWindowController showWindow:]`.
|
||||
/// Notable, this handles passing the implementing entity as the delegate, ensuring that
|
||||
/// callbacks work appropriately.
|
||||
///
|
||||
/// We're technically setting the delegate later than is ideal, but in practice it works fine
|
||||
/// in most cases due to the underlying implementation of `NSWindow` deferring things until
|
||||
/// needed.
|
||||
fn show(&self);
|
||||
|
||||
/// Calls through to the native NSwindow close implementation.
|
||||
fn close(&self);
|
||||
}
|
||||
|
||||
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
|
||||
/// lifecycle methods, but mix in a few extra things to handle offering configuration tools
|
||||
/// in lieu of subclasses.
|
||||
|
@ -67,7 +17,7 @@ pub trait WindowController {
|
|||
/// to set up your views and what not.
|
||||
///
|
||||
/// If you're coming from the web, you can think of this as `DOMContentLoaded`.
|
||||
fn did_load(&self) {}
|
||||
fn did_load(&mut self, _window: WindowHandle) {}
|
||||
|
||||
/// Fires when a window is going to close. You might opt to, say, clean up things here -
|
||||
/// perhaps you have a long running task, or something that should be removed.
|
||||
|
|
|
@ -7,24 +7,15 @@ use std::cell::RefCell;
|
|||
use cocoa::base::{id, nil, YES, NO};
|
||||
use cocoa::foundation::{NSSize, NSString};
|
||||
|
||||
use objc_id::Id;
|
||||
use objc::runtime::Object;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::view::{ViewController, ViewWrapper};
|
||||
use crate::constants::WINDOW_CONTROLLER_PTR;
|
||||
use crate::toolbar::{Toolbar, ToolbarDelegate};
|
||||
use crate::window::WindowController;
|
||||
use crate::window::controller::{register_window_controller_class};
|
||||
|
||||
static WINDOW_CONTROLLER_PTR: &str = "rstWindowController";
|
||||
|
||||
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
|
||||
/// where our `NSWindow` and associated delegate live.
|
||||
#[derive(Default)]
|
||||
pub struct WindowInner {
|
||||
pub controller: Option<Id<Object>>,
|
||||
pub toolbar: Option<Toolbar>
|
||||
}
|
||||
use crate::view::traits::Node;
|
||||
use crate::window::handle::WindowHandle;
|
||||
use crate::window::traits::WindowController;
|
||||
use crate::window::controller::register_window_controller_class;
|
||||
|
||||
pub enum WindowTitleVisibility {
|
||||
Visible,
|
||||
|
@ -40,8 +31,18 @@ impl From<WindowTitleVisibility> for usize {
|
|||
}
|
||||
}
|
||||
|
||||
impl WindowInner {
|
||||
/// Configures the `NSWindow` to know about our delegate.
|
||||
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
|
||||
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
|
||||
#[derive(Clone)]
|
||||
pub struct Window<T> {
|
||||
internal_callback_ptr: *const RefCell<T>,
|
||||
pub objc_controller: WindowHandle,
|
||||
pub controller: Rc<RefCell<T>>
|
||||
}
|
||||
|
||||
impl<T> Window<T> where T: WindowController + 'static {
|
||||
/// Allocates and configures a `WindowController` in the Objective-C/Cocoa runtime that maps over
|
||||
/// to your supplied controller.
|
||||
///
|
||||
/// Now, you may look at this and go "hey, the hell is going on here - why don't you make the
|
||||
/// `NSWindow` in `[NSWindowController loadWindow]`?
|
||||
|
@ -54,246 +55,74 @@ impl WindowInner {
|
|||
/// the route of implementing `loadView`.
|
||||
///
|
||||
/// APPKIT!
|
||||
pub fn configure<T: WindowController + 'static>(&mut self, window_controller: &T) {
|
||||
let window = window_controller.config().0;
|
||||
pub fn new(controller: T) -> Self {
|
||||
let window = controller.config().0;
|
||||
let controller = Rc::new(RefCell::new(controller));
|
||||
|
||||
self.controller = Some(unsafe {
|
||||
let internal_callback_ptr = {
|
||||
let cloned = Rc::clone(&controller);
|
||||
Rc::into_raw(cloned)
|
||||
};
|
||||
|
||||
let inner = unsafe {
|
||||
let window_controller_class = register_window_controller_class::<T>();
|
||||
let controller_alloc: id = msg_send![window_controller_class, alloc];
|
||||
let controller: id = msg_send![controller_alloc, initWithWindow:window];
|
||||
(&mut *controller).set_ivar(WINDOW_CONTROLLER_PTR, window_controller as *const T as usize);
|
||||
(&mut *controller).set_ivar(WINDOW_CONTROLLER_PTR, internal_callback_ptr as usize);
|
||||
|
||||
let window: id = msg_send![controller, window];
|
||||
let _: () = msg_send![window, setDelegate:controller];
|
||||
|
||||
Id::from_ptr(controller)
|
||||
});
|
||||
ShareId::from_ptr(controller)
|
||||
};
|
||||
|
||||
{
|
||||
let mut vc = controller.borrow_mut();
|
||||
(*vc).did_load(WindowHandle(Some(inner.clone())));
|
||||
}
|
||||
|
||||
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
|
||||
/// to the Objective C runtime.
|
||||
pub fn set_title(&mut self, title: &str) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitle:title];
|
||||
}
|
||||
Window {
|
||||
internal_callback_ptr: internal_callback_ptr,
|
||||
objc_controller: WindowHandle(Some(inner)),
|
||||
controller: controller
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the title visibility for the underlying window.
|
||||
pub fn set_title_visibility(&mut self, visibility: usize) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitleVisibility:visibility];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for configuring whether the window is movable via the background.
|
||||
pub fn set_movable_by_background(&self, movable: bool) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setMovableByWindowBackground:match movable {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting whether this titlebar appears transparent.
|
||||
pub fn set_titlebar_appears_transparent(&self, transparent: bool) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setTitlebarAppearsTransparent:match transparent {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting this Window autosave name.
|
||||
pub fn set_autosave_name(&mut self, name: &str) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
|
||||
// Now we need to make sure to re-apply the NSAutoSaveName, as initWithWindow
|
||||
// strips it... for some reason. We want it applied as it does nice things like
|
||||
// save the window position in the Defaults database, which is what users expect.
|
||||
let autosave = NSString::alloc(nil).init_str(name);
|
||||
let _: () = msg_send![window, setFrameAutosaveName:autosave];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let size = NSSize::new(width.into(), height.into());
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setMinSize:size];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used for setting a toolbar on this window. Note that this takes ownership of whatever
|
||||
/// `ToolbarDelegate` you pass! The underlying `NSToolbar` is a bit... old, and it's just
|
||||
/// easier to do things this way.
|
||||
///
|
||||
/// If you find yourself in a position where you need your toolbar after the fact, you
|
||||
/// probably have bigger issues.
|
||||
pub fn set_toolbar<T: ToolbarDelegate + 'static>(&mut self, identifier: &str, toolbar: T) {
|
||||
let toolbar = Toolbar::new(identifier, toolbar);
|
||||
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setToolbar:&*toolbar.inner];
|
||||
}
|
||||
}
|
||||
|
||||
self.toolbar = Some(toolbar);
|
||||
}
|
||||
|
||||
/// Used for setting the content view controller for this window.
|
||||
pub fn set_content_view<T: ViewController + ViewWrapper + 'static>(&mut self, view_controller: &T) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
if let Some(vc) = view_controller.get_handle() {
|
||||
let _: () = msg_send![*controller, setContentViewController:&*vc];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the
|
||||
/// most common use case, hence why this method was chosen - if you want or need something
|
||||
/// else, feel free to open an issue to discuss.
|
||||
///
|
||||
/// You should never be calling this yourself, mind you - Alchemy core handles this for you.
|
||||
pub fn show(&self) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let _: () = msg_send![*controller, showWindow:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
|
||||
/// window.
|
||||
///
|
||||
/// I dunno what else to say here, lol.
|
||||
pub fn close(&self) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let _: () = msg_send![*controller, close];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowInner {
|
||||
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
|
||||
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
|
||||
/// safer than sorry.
|
||||
fn drop(&mut self) {
|
||||
if let Some(controller) = &self.controller {
|
||||
unsafe {
|
||||
let window: id = msg_send![*controller, window];
|
||||
let _: () = msg_send![window, setDelegate:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Window wraps `NSWindowController`, using interior mutability to handle configuration and calling
|
||||
/// through to it.
|
||||
///
|
||||
/// Why `NSWindowController` and not `NSWindow`, you ask? The former has lifecycle events we're
|
||||
/// interested in, the latter is... well, just the window.
|
||||
#[derive(Default)]
|
||||
pub struct Window(Rc<RefCell<WindowInner>>);
|
||||
|
||||
impl Window {
|
||||
/// Sets the window title.
|
||||
/// Sets the title for this window.
|
||||
pub fn set_title(&self, title: &str) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
window.set_title(title);
|
||||
}
|
||||
|
||||
/// Sets the window title visibility.
|
||||
pub fn set_title_visibility(&self, visibility: usize) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
window.set_title_visibility(visibility);
|
||||
}
|
||||
|
||||
/// Sets whether the window is movable by the background or not.
|
||||
pub fn set_movable_by_background(&self, movable: bool) {
|
||||
let window = self.0.borrow();
|
||||
window.set_movable_by_background(movable);
|
||||
}
|
||||
|
||||
/// Sets whether the titlebar appears transparent or not.
|
||||
pub fn set_titlebar_appears_transparent(&self, transparent: bool) {
|
||||
let window = self.0.borrow();
|
||||
window.set_titlebar_appears_transparent(transparent);
|
||||
}
|
||||
|
||||
/// Set the window autosave name, which preserves things like position across restarts.
|
||||
pub fn set_autosave_name(&self, name: &str) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
window.set_autosave_name(name);
|
||||
}
|
||||
|
||||
/// Sets the window's smallest size it can shrink to.
|
||||
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
|
||||
let window = self.0.borrow_mut();
|
||||
window.set_minimum_content_size(width, height);
|
||||
}
|
||||
|
||||
/// Sets the Toolbar for this window. Note that this takes ownership of the toolbar!
|
||||
pub fn set_toolbar<T: ToolbarDelegate + 'static>(&self, identifier: &str, toolbar: T) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
window.set_toolbar(identifier, toolbar);
|
||||
self.objc_controller.set_title(title.into());
|
||||
}
|
||||
|
||||
/// Sets the content view controller for the window.
|
||||
pub fn set_content_view<T: ViewController + ViewWrapper + 'static>(&self, view: &T) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
window.set_content_view(view);
|
||||
pub fn set_content_view_controller<VC: Node + 'static>(&self, view_controller: &VC) {
|
||||
self.objc_controller.set_content_view_controller(view_controller);
|
||||
}
|
||||
|
||||
/// Shows the window, running a configuration pass if necessary.
|
||||
pub fn show<T: WindowController + 'static>(&self, controller: &T) {
|
||||
let did_load = {
|
||||
let mut window = self.0.borrow_mut();
|
||||
|
||||
if window.controller.is_none() {
|
||||
window.configure(controller);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if did_load {
|
||||
controller.did_load();
|
||||
}
|
||||
|
||||
let window = self.0.borrow();
|
||||
window.show();
|
||||
pub fn show(&self) {
|
||||
self.objc_controller.show();
|
||||
}
|
||||
|
||||
/// Closes the window.
|
||||
pub fn close(&self) {
|
||||
let window = self.0.borrow();
|
||||
window.close();
|
||||
self.objc_controller.close();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for Window<T> {
|
||||
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
|
||||
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
|
||||
/// safer than sorry.
|
||||
///
|
||||
/// We also clean up our loopback pointer that we use for callbacks.
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Some(objc_controller) = &self.objc_controller.0 {
|
||||
let window: id = msg_send![*objc_controller, window];
|
||||
let _: () = msg_send![window, setDelegate:nil];
|
||||
}
|
||||
|
||||
let _ = Rc::from_raw(self.internal_callback_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue