From f49eff24f9040ed2abaafe3a7b059b272da5dab1 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 11 Mar 2020 17:56:17 -0700 Subject: [PATCH] Devised a better (safer) way to handle UI/Controller/Delegate setup --- appkit/src/app/mod.rs | 11 +- appkit/src/constants.rs | 1 + appkit/src/lib.rs | 6 +- appkit/src/view/class.rs | 94 ++++++++--- appkit/src/view/mod.rs | 2 +- appkit/src/view/traits.rs | 13 +- appkit/src/view/view.rs | 123 ++++++++------ appkit/src/webview/webview.rs | 2 +- appkit/src/window/handle.rs | 147 +++++++++++++++++ appkit/src/window/mod.rs | 5 +- appkit/src/window/traits.rs | 54 +----- appkit/src/window/window.rs | 301 ++++++++-------------------------- 12 files changed, 385 insertions(+), 374 deletions(-) create mode 100644 appkit/src/window/handle.rs diff --git a/appkit/src/app/mod.rs b/appkit/src/app/mod.rs index d5739f2..c085fe1 100644 --- a/appkit/src/app/mod.rs +++ b/appkit/src/app/mod.rs @@ -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 App where M: Send + Sync + 'static, T: AppDelegate { +impl App where M: Send + Sync + 'static, T: AppDelegate + Dispatcher { /// 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) { diff --git a/appkit/src/constants.rs b/appkit/src/constants.rs index 0115fc9..af77297 100644 --- a/appkit/src/constants.rs +++ b/appkit/src/constants.rs @@ -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"; diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index c60a1c1..d6f35b4 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -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 diff --git a/appkit/src/view/class.rs b/appkit/src/view/class.rs index b8625ae..263a979 100644 --- a/appkit/src/view/class.rs +++ b/appkit/src/view/class.rs @@ -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(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 { - info: Id::from_ptr(info) - }).into() + let view_ptr = ptr as *const RefCell; + 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(this: &mut Object, _: Sel, info: i extern fn prepare_for_drag_operation(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; + let view = Rc::from_raw(view_ptr); + + let response = { + let v = view.borrow(); + + match (*v).prepare_for_drag_operation(DragInfo { + info: Id::from_ptr(info) + }) { + true => YES, + false => NO + } + }; - match (*view).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(this: &mut Object, _: Se extern fn perform_drag_operation(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; + let view = Rc::from_raw(view_ptr); + + let response = { + let v = view.borrow(); + + match (*v).perform_drag_operation(DragInfo { + info: Id::from_ptr(info) + }) { + true => YES, + false => NO + } + }; - match (*view).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(this: &mut Object, _: Sel, i extern fn conclude_drag_operation(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; + let view = Rc::from_raw(view_ptr); + + let response = { + let v = view.borrow(); + (*v).conclude_drag_operation(DragInfo { + info: Id::from_ptr(info) + }); + }; - (*view).conclude_drag_operation(DragInfo { - info: Id::from_ptr(info) - }); + Rc::into_raw(view); + response } } @@ -95,10 +129,18 @@ extern fn conclude_drag_operation(this: &mut Object, _: Sel, extern fn dragging_exited(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 { - info: Id::from_ptr(info) - }); + let view_ptr = ptr as *const RefCell; + 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 } } diff --git a/appkit/src/view/mod.rs b/appkit/src/view/mod.rs index f4be8f3..5cd8f49 100644 --- a/appkit/src/view/mod.rs +++ b/appkit/src/view/mod.rs @@ -6,4 +6,4 @@ pub mod traits; pub use traits::*; pub mod view; -pub use view::View; +pub use view::{View, ViewHandle}; diff --git a/appkit/src/view/traits.rs b/appkit/src/view/traits.rs index 904d673..408b426 100644 --- a/appkit/src/view/traits.rs +++ b/appkit/src/view/traits.rs @@ -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>; +pub trait Node { + fn get_backing_node(&self) -> Option>; } 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 } diff --git a/appkit/src/view/view.rs b/appkit/src/view/view.rs index e587918..eaa6030 100644 --- a/appkit/src/view/view.rs +++ b/appkit/src/view/view.rs @@ -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> -} +/// 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>); -impl ViewInner { - pub fn configure(&mut self, controller: &T) { - self.controller = Some(unsafe { - let view_controller: id = msg_send![register_controller_class::(), 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>); +/// 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 { + internal_callback_ptr: *const RefCell, + pub objc_controller: ViewHandle, + pub controller: Rc> +} + +impl View 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::(), 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(&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> { - 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 Node for View { + /// Returns the Objective-C object used for handling the view heirarchy. + fn get_backing_node(&self) -> Option> { + self.objc_controller.0.clone() + } +} + +impl std::fmt::Debug for View { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "View ({:p})", self) } } + +impl Drop for View { + /// A bit of extra cleanup for delegate callback pointers. + fn drop(&mut self) { + unsafe { + let _ = Rc::from_raw(self.internal_callback_ptr); + } + } +} diff --git a/appkit/src/webview/webview.rs b/appkit/src/webview/webview.rs index 9726a0a..4fa90ec 100644 --- a/appkit/src/webview/webview.rs +++ b/appkit/src/webview/webview.rs @@ -103,7 +103,7 @@ impl WebView { webview.configure(controller); } - controller.did_load(); + //controller.did_load(); } pub fn get_handle(&self) -> Option> { diff --git a/appkit/src/window/handle.rs b/appkit/src/window/handle.rs new file mode 100644 index 0000000..c0317aa --- /dev/null +++ b/appkit/src/window/handle.rs @@ -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>); + +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>(&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(&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(&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]; + } + } + } +} diff --git a/appkit/src/window/mod.rs b/appkit/src/window/mod.rs index 34cbaba..f7ec7f9 100644 --- a/appkit/src/window/mod.rs +++ b/appkit/src/window/mod.rs @@ -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}; diff --git a/appkit/src/window/traits.rs b/appkit/src/window/traits.rs index 50a94fa..499245a 100644 --- a/appkit/src/window/traits.rs +++ b/appkit/src/window/traits.rs @@ -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. diff --git a/appkit/src/window/window.rs b/appkit/src/window/window.rs index 5ea455e..1f7d705 100644 --- a/appkit/src/window/window.rs +++ b/appkit/src/window/window.rs @@ -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>, - pub toolbar: Option -} +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 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 { + internal_callback_ptr: *const RefCell, + pub objc_controller: WindowHandle, + pub controller: Rc> +} + +impl Window 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(&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)); + + let internal_callback_ptr = { + let cloned = Rc::clone(&controller); + Rc::into_raw(cloned) + }; - self.controller = Some(unsafe { + let inner = unsafe { let window_controller_class = register_window_controller_class::(); 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]; + let _: () = msg_send![window, setDelegate:controller]; + + ShareId::from_ptr(controller) + }; - Id::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>(&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(&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(&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>); - -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>(&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(&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(&self, view: &T) { - let mut window = self.0.borrow_mut(); - window.set_content_view(view); + pub fn set_content_view_controller(&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(&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 Drop for Window { + /// 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); + } } }