diff --git a/appkit/Cargo.toml b/appkit/Cargo.toml index 17e0f0c..92a17cb 100644 --- a/appkit/Cargo.toml +++ b/appkit/Cargo.toml @@ -11,10 +11,10 @@ dispatch = "0.2.0" libc = "0.2" objc = "0.2.7" objc_id = "0.1.1" -#uuid = { version = "0.8", features = ["v4"] } +uuid = { version = "0.8", features = ["v4"], optional = true } url = "2.1.1" [features] cloudkit = [] -user-notifications = [] +user-notifications = ["uuid"] enable-webview-downloading = [] diff --git a/appkit/src/filesystem/enums.rs b/appkit/src/filesystem/enums.rs index 1802418..71c051b 100644 --- a/appkit/src/filesystem/enums.rs +++ b/appkit/src/filesystem/enums.rs @@ -1,6 +1,6 @@ //! Certain enums that are useful (response types, etc). -use cocoa::foundation::{NSInteger, NSUInteger}; +use crate::foundation::{NSInteger, NSUInteger}; pub enum ModalResponse { Ok, diff --git a/appkit/src/filesystem/manager.rs b/appkit/src/filesystem/manager.rs index cce8150..607eab3 100644 --- a/appkit/src/filesystem/manager.rs +++ b/appkit/src/filesystem/manager.rs @@ -4,16 +4,14 @@ use std::error::Error; use std::sync::RwLock; -use cocoa::base::{id, nil, NO}; -use cocoa::foundation::{NSString, NSUInteger}; use objc_id::Id; use objc::runtime::{BOOL, Object}; use objc::{class, msg_send, sel, sel_impl}; use url::Url; +use crate::foundation::{id, nil, NO, NSString, NSUInteger}; use crate::error::AppKitError; use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask}; -use crate::utils::str_from; pub struct FileManager { pub manager: RwLock> @@ -58,8 +56,7 @@ impl FileManager { create:NO error:nil]; - let s: id = msg_send![dir, absoluteString]; - str_from(s) + NSString::wrap(msg_send![dir, absoluteString]).to_str() }; Url::parse(directory).map_err(|e| e.into()) @@ -69,12 +66,12 @@ impl FileManager { /// an error on the Objective-C side, which we attempt to handle and bubble up as a result if /// so. pub fn move_item(&self, from: Url, to: Url) -> Result<(), Box> { - unsafe { - let s = NSString::alloc(nil).init_str(from.as_str()); - let from_url: id = msg_send![class!(NSURL), URLWithString:s]; + let from = NSString::new(from.as_str()); + let to = NSString::new(to.as_str()); - let s2 = NSString::alloc(nil).init_str(to.as_str()); - let to_url: id = msg_send![class!(NSURL), URLWithString:s2]; + unsafe { + let from_url: id = msg_send![class!(NSURL), URLWithString:from.into_inner()]; + let to_url: id = msg_send![class!(NSURL), URLWithString:to.into_inner()]; // This should potentially be write(), but the backing class handles this logic // already, so... going to leave it as read. diff --git a/appkit/src/filesystem/save.rs b/appkit/src/filesystem/save.rs index 88da1f9..e7f9e01 100644 --- a/appkit/src/filesystem/save.rs +++ b/appkit/src/filesystem/save.rs @@ -4,14 +4,11 @@ use block::ConcreteBlock; -use cocoa::base::{id, nil, YES, NO}; -use cocoa::foundation::{NSInteger, NSString}; - use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; -use crate::utils::str_from; +use crate::foundation::{id, nil, YES, NO, NSInteger, NSString}; #[derive(Debug)] pub struct FileSavePanel { @@ -54,7 +51,7 @@ impl FileSavePanel { pub fn set_suggested_filename(&mut self, suggested_filename: &str) { unsafe { - let filename = NSString::alloc(nil).init_str(suggested_filename); + let filename = NSString::new(suggested_filename); let _: () = msg_send![&*self.panel, setNameFieldStringValue:filename]; } } @@ -100,11 +97,12 @@ impl FileSavePanel { pub fn get_url(panel: &Object) -> Option { unsafe { let url: id = msg_send![&*panel, URL]; + if url == nil { None } else { let path: id = msg_send![url, path]; - Some(str_from(path).to_string()) + Some(NSString::wrap(path).to_str().to_string()) } } } diff --git a/appkit/src/filesystem/select.rs b/appkit/src/filesystem/select.rs index 1c24cbb..be54fe1 100644 --- a/appkit/src/filesystem/select.rs +++ b/appkit/src/filesystem/select.rs @@ -4,15 +4,12 @@ use block::ConcreteBlock; -use cocoa::base::{id, YES, NO}; -use cocoa::foundation::NSInteger; - use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; +use crate::foundation::{id, YES, NO, NSInteger, NSString}; use crate::filesystem::enums::ModalResponse; -use crate::utils::str_from; #[derive(Debug)] pub struct FileSelectPanel { @@ -156,8 +153,8 @@ pub fn get_urls(panel: &Object) -> Vec { } let url: id = msg_send![urls, objectAtIndex:count-1]; - let path: id = msg_send![url, absoluteString]; - paths.push(str_from(path).to_string()); + let path = NSString::wrap(msg_send![url, absoluteString]).to_str().to_string(); + paths.push(path); count -= 1; } } diff --git a/appkit/src/geometry.rs b/appkit/src/geometry.rs index 7094f3b..6466655 100644 --- a/appkit/src/geometry.rs +++ b/appkit/src/geometry.rs @@ -18,6 +18,11 @@ pub struct Rect { } impl Rect { + /// Returns a new `Rect` initialized with the values specified. + pub fn new(top: f64, left: f64, width: f64, height: f64) -> Self { + Rect { top: top, left: left, width: width, height: height } + } + /// Returns a zero'd out Rect, with f64 (32-bit is mostly dead on Cocoa, so... this is "okay"). pub fn zero() -> Rect { Rect { diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index c29925d..bdc94e6 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -29,7 +29,7 @@ pub mod constants; pub mod dragdrop; pub mod error; pub mod events; -//pub mod filesystem; +pub mod filesystem; pub mod foundation; pub mod geometry; pub mod layout; @@ -44,8 +44,8 @@ pub mod printing; pub mod toolbar; pub mod user_activity; pub mod utils; -/*pub mod view; -pub mod webview; +pub mod view; +//pub mod webview; pub mod window; // We re-export these so that they can be used without increasing build times. @@ -69,13 +69,9 @@ pub mod prelude { Window, WindowController, WindowHandle }; - pub use crate::webview::{ - WebView, WebViewConfig, WebViewController - }; + //pub use crate::webview::{ + // WebView, WebViewConfig, WebViewController + //}; pub use crate::view::{View, ViewHandle, ViewController}; - - pub use appkit_derive::{ - WindowWrapper, ViewWrapper - }; -}*/ +} diff --git a/appkit/src/notifications/center.rs b/appkit/src/notifications/center.rs deleted file mode 100644 index 73439a9..0000000 --- a/appkit/src/notifications/center.rs +++ /dev/null @@ -1,62 +0,0 @@ -//! Wraps UNUserNotificationCenter for macOS. Note that this uses the newer -//! `UserNotifications.framework` API, which requires that your application be properly signed. - -use block::ConcreteBlock; - -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; - -use objc::{class, msg_send, sel, sel_impl}; - -use crate::notifications::Notification; -use crate::utils::str_from; - -#[allow(non_upper_case_globals, non_snake_case)] -pub mod NotificationAuthOption { - pub const Badge: i32 = 1 << 0; - pub const Sound: i32 = 1 << 1; - pub const Alert: i32 = 1 << 2; -} - -/// Acts as a central interface to the Notification Center on macOS. -pub struct NotificationCenter; - -impl NotificationCenter { - /// Requests authorization from the user to send them notifications. - pub fn request_authorization(options: i32) { - unsafe { - let block = ConcreteBlock::new(|_: id, error: id| { - let msg: id = msg_send![error, localizedDescription]; - - let localized_description = str_from(msg); - if localized_description != "" { - println!("{:?}", localized_description); - } - }); - - let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; - let _: () = msg_send![center, requestAuthorizationWithOptions:options completionHandler:block.copy()]; - } - } - - /// Queues up a `Notification` to be displayed to the user. - pub fn notify(notification: Notification) { - let uuidentifier = format!("{}", uuid::Uuid::new_v4()); - - unsafe { - let identifier = NSString::alloc(nil).init_str(&uuidentifier); - let request: id = msg_send![class!(UNNotificationRequest), requestWithIdentifier:identifier content:&*notification.inner trigger:nil]; - - let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; - let _: () = msg_send![center, addNotificationRequest:request]; - } - } - - /// Removes all notifications that have been delivered (e.g, in the notification center). - pub fn remove_all_delivered_notifications() { - unsafe { - let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; - let _: () = msg_send![center, removeAllDeliveredNotifications]; - } - } -} diff --git a/appkit/src/notifications/enums.rs b/appkit/src/notifications/enums.rs new file mode 100644 index 0000000..00b4d96 --- /dev/null +++ b/appkit/src/notifications/enums.rs @@ -0,0 +1,29 @@ +//! Enums used in notifications - e.g, for customizing registration or appearance. + +use crate::foundation::NSUInteger; + +pub enum NotificationAuthOption { + Badge, + Sound, + Alert +} + +impl From for NSUInteger { + fn from(option: NotificationAuthOption) -> Self { + match option { + NotificationAuthOption::Badge => 1 << 0, + NotificationAuthOption::Sound => 1 << 1, + NotificationAuthOption::Alert => 1 << 2 + } + } +} + +impl From<&NotificationAuthOption> for NSUInteger { + fn from(option: &NotificationAuthOption) -> Self { + match option { + NotificationAuthOption::Badge => 1 << 0, + NotificationAuthOption::Sound => 1 << 1, + NotificationAuthOption::Alert => 1 << 2 + } + } +} diff --git a/appkit/src/notifications/mod.rs b/appkit/src/notifications/mod.rs index d6e90bd..8d591f0 100644 --- a/appkit/src/notifications/mod.rs +++ b/appkit/src/notifications/mod.rs @@ -1,7 +1,65 @@ -//! Hoisting. +//! Wraps UNUserNotificationCenter for macOS. Note that this uses the newer +//! `UserNotifications.framework` API, which requires that your application be properly signed. +//! +//! To use this module, you must specify the `user-notifications` feature flag in your +//! `Cargo.toml`. -pub mod center; -pub use center::*; +use block::ConcreteBlock; + +use objc::{class, msg_send, sel, sel_impl}; +use uuid::Uuid; + +use crate::foundation::{id, nil, NSString, NSUInteger}; + +pub mod enums; +pub use enums::NotificationAuthOption; pub mod notifications; -pub use notifications::*; +pub use notifications::Notification; + +/// Acts as a central interface to the Notification Center on macOS. +pub struct NotificationCenter; + +impl NotificationCenter { + /// Requests authorization from the user to send them notifications. + pub fn request_authorization(options: &[NotificationAuthOption]) { + unsafe { + // @TODO: Revisit. + let block = ConcreteBlock::new(|_: id, error: id| { + let localized_description = NSString::new(msg_send![error, localizedDescription]).to_str(); + if localized_description != "" { + println!("{:?}", localized_description); + } + }); + + let mut opts: NSUInteger = 0; + for opt in options { + let o: NSUInteger = opt.into(); + opts = opts << o; + } + + let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; + let _: () = msg_send![center, requestAuthorizationWithOptions:opts completionHandler:block.copy()]; + } + } + + /// Queues up a `Notification` to be displayed to the user. + pub fn notify(notification: Notification) { + let uuidentifier = format!("{}", Uuid::new_v4()); + + unsafe { + let identifier = NSString::new(&uuidentifier); + let request: id = msg_send![class!(UNNotificationRequest), requestWithIdentifier:identifier content:&*notification.0 trigger:nil]; + let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; + let _: () = msg_send![center, addNotificationRequest:request]; + } + } + + /// Removes all notifications that have been delivered (e.g, in the notification center). + pub fn remove_all_delivered_notifications() { + unsafe { + let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter]; + let _: () = msg_send![center, removeAllDeliveredNotifications]; + } + } +} diff --git a/appkit/src/notifications/notifications.rs b/appkit/src/notifications/notifications.rs index 4ee464f..9a5dc37 100644 --- a/appkit/src/notifications/notifications.rs +++ b/appkit/src/notifications/notifications.rs @@ -1,36 +1,28 @@ //! Acts as a (currently dumb) wrapper for `UNMutableNotificationContent`, which is what you mostly //! need to pass to the notification center for things to work. -use cocoa::base::{id, nil}; -use cocoa::foundation::NSString; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, NSString}; + /// A wrapper for `UNMutableNotificationContent`. Retains the pointer from the Objective C side, /// and is ultimately dropped upon sending. -pub struct Notification { - pub inner: Id -} +pub struct Notification(pub Id); impl Notification { /// Constructs a new `Notification`. This allocates `NSString`'s, as it has to do so for the /// Objective C runtime - be aware if you're slaming this (you shouldn't be slamming this). pub fn new(title: &str, body: &str) -> Self { - Notification { - inner: unsafe { - let cls = class!(UNMutableNotificationContent); - let content: id = msg_send![cls, new]; + let title = NSString::new(title); + let body = NSString::new(body); - let title = NSString::alloc(nil).init_str(title); - let _: () = msg_send![content, setTitle:title]; - - let body = NSString::alloc(nil).init_str(body); - let _: () = msg_send![content, setBody:body]; - - Id::from_ptr(content) - } - } + Notification(unsafe { + let content: id = msg_send![class!(UNMutableNotificationContent), new]; + let _: () = msg_send![content, setTitle:title]; + let _: () = msg_send![content, setBody:body]; + Id::from_ptr(content) + }) } } diff --git a/appkit/src/view/class.rs b/appkit/src/view/class.rs index 2bcd529..cad6e19 100644 --- a/appkit/src/view/class.rs +++ b/appkit/src/view/class.rs @@ -10,20 +10,17 @@ use std::rc::Rc; use std::sync::Once; -use cocoa::base::{id, nil, YES, NO}; -use cocoa::foundation::{NSUInteger}; - use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel, BOOL}; use objc::{msg_send, sel, sel_impl}; use objc_id::Id; +use crate::foundation::{id, nil, YES, NO, NSUInteger}; use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR}; use crate::dragdrop::DragInfo; use crate::view::traits::ViewController; use crate::utils::load; - /// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though. extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL { return YES; diff --git a/appkit/src/view/controller.rs b/appkit/src/view/controller.rs index 9312672..aa9ba46 100644 --- a/appkit/src/view/controller.rs +++ b/appkit/src/view/controller.rs @@ -3,13 +3,11 @@ use std::sync::Once; -use cocoa::base::{id, NO}; -use cocoa::foundation::{NSRect}; - use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, NO, CGRect}; use crate::constants::VIEW_CONTROLLER_PTR; use crate::geometry::Rect; use crate::view::ViewController; @@ -18,7 +16,7 @@ use crate::view::class::register_view_class; /// Loads and configures ye old NSView for this controller. extern fn load_view(this: &mut Object, _: Sel) { unsafe { - let zero: NSRect = Rect::zero().into(); + let zero: CGRect = Rect::zero().into(); let view: id = msg_send![register_view_class::(), new]; let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; let _: () = msg_send![view, setFrame:zero]; diff --git a/appkit/src/view/view.rs b/appkit/src/view/handle.rs similarity index 50% rename from appkit/src/view/view.rs rename to appkit/src/view/handle.rs index 7c174e1..5694f7f 100644 --- a/appkit/src/view/view.rs +++ b/appkit/src/view/handle.rs @@ -1,21 +1,19 @@ -//! A wrapper for `NSViewController`. Uses interior mutability to - -use std::cell::RefCell; -use std::rc::Rc; - -use cocoa::base::{id, nil, YES}; -use cocoa::foundation::NSArray; +//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your +//! `ViewController::did_load()` method. This method is safe to store and use, however as it's +//! UI-specific it's not thread safe. +//! +//! You can use this struct to configure how a view should look and layout. It implements +//! AutoLayout - for more information, see the AutoLayout tutorial. use objc_id::ShareId; use objc::runtime::Object; use objc::{msg_send, sel, sel_impl}; +use crate::foundation::{id, YES, NSArray, NSString}; use crate::color::Color; -use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR}; +use crate::constants::BACKGROUND_COLOR; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::pasteboard::PasteboardType; -use crate::view::controller::register_controller_class; -use crate::view::traits::ViewController; /// 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 @@ -84,12 +82,15 @@ impl ViewHandle { pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { if let Some(objc) = &self.objc { unsafe { - let types = NSArray::arrayWithObjects(nil, &types.iter().map(|t| { - t.to_nsstring() - }).collect::>()); + let types: NSArray = types.into_iter().map(|t| { + // This clone probably doesn't need to be here, but it should also be cheap as + // this is just an enum... and this is not an oft called method. + let x: NSString = t.clone().into(); + x.into_inner() + }).collect::>().into(); let view: id = msg_send![*objc, view]; - let _: () = msg_send![view, registerForDraggedTypes:types]; + let _: () = msg_send![view, registerForDraggedTypes:types.into_inner()]; } } } @@ -108,107 +109,3 @@ impl ViewHandle { } } } - -/// 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) - }; - - let handle = ViewHandle::new(inner); - - { - let mut vc = controller.borrow_mut(); - (*vc).did_load(handle.clone()); - } - - View { - internal_callback_ptr: internal_callback_ptr, - objc_controller: handle, - controller: controller - } - } - - pub fn set_background_color(&self, color: Color) { - self.objc_controller.set_background_color(color); - } - - pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { - self.objc_controller.register_for_dragged_types(types); - } - - pub fn top(&self) -> &LayoutAnchorY { - &self.objc_controller.top - } - - pub fn leading(&self) -> &LayoutAnchorX { - &self.objc_controller.leading - } - - pub fn trailing(&self) -> &LayoutAnchorX { - &self.objc_controller.trailing - } - - pub fn bottom(&self) -> &LayoutAnchorY { - &self.objc_controller.bottom - } - - pub fn width(&self) -> &LayoutAnchorDimension { - &self.objc_controller.width - } - - pub fn height(&self) -> &LayoutAnchorDimension { - &self.objc_controller.height - } -} - -impl Layout for View { - /// Returns the Objective-C object used for handling the view heirarchy. - fn get_backing_node(&self) -> Option> { - self.objc_controller.objc.clone() - } - - fn add_subview(&self, subview: &V) { - self.objc_controller.add_subview(subview); - } -} - -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/view/mod.rs b/appkit/src/view/mod.rs index 5cd8f49..01583ce 100644 --- a/appkit/src/view/mod.rs +++ b/appkit/src/view/mod.rs @@ -1,9 +1,156 @@ +//! This module implements tooling for constructing Views. Notably, it provides the following: +//! +//! - A `View` type, which holds your `impl ViewController` and handles routing around platform +//! lifecycle methods accordingly. This is a heap allocation. +//! - A `ViewController` trait, which enables you to hook into the `NSViewController` lifecycle +//! methods. +//! - A `ViewHandle` struct, which wraps a platform-provided `NSView` and enables you to configure +//! things such as appearance and layout. +//! +//! You can use it like the following: +//! +//! ``` +//! use appkit::prelude::{View, ViewController, ViewHandle}; +//! use appkit::color::rgb; +//! +//! #[derive(Default)] +//! pub struct MyView { +//! pub view: ViewHandle +//! } +//! +//! impl ViewController for MyView { +//! fn did_load(&mut self, view: ViewHandle) { +//! self.view = view; +//! self.view.set_background_color(rgb(0, 0, 0)); +//! } +//! } +//! ``` +//! +//! For more information and examples, consult the sample code distributed in the git repository. -pub(crate) mod class; -pub(crate) mod controller; +use std::rc::Rc; +use std::cell::RefCell; + +use objc_id::ShareId; +use objc::runtime::Object; +use objc::{msg_send, sel, sel_impl}; + +use crate::foundation::id; +use crate::color::Color; +use crate::constants::VIEW_CONTROLLER_PTR; +use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; +use crate::pasteboard::PasteboardType; + +mod class; +mod controller; +use controller::register_controller_class; pub mod traits; -pub use traits::*; +pub use traits::ViewController; -pub mod view; -pub use view::{View, ViewHandle}; +pub mod handle; +pub use handle::ViewHandle; + +/// 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) + }; + + let handle = ViewHandle::new(inner); + + { + let mut vc = controller.borrow_mut(); + (*vc).did_load(handle.clone()); + } + + View { + internal_callback_ptr: internal_callback_ptr, + objc_controller: handle, + controller: controller + } + } + + pub fn set_background_color(&self, color: Color) { + self.objc_controller.set_background_color(color); + } + + pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { + self.objc_controller.register_for_dragged_types(types); + } + + pub fn top(&self) -> &LayoutAnchorY { + &self.objc_controller.top + } + + pub fn leading(&self) -> &LayoutAnchorX { + &self.objc_controller.leading + } + + pub fn trailing(&self) -> &LayoutAnchorX { + &self.objc_controller.trailing + } + + pub fn bottom(&self) -> &LayoutAnchorY { + &self.objc_controller.bottom + } + + pub fn width(&self) -> &LayoutAnchorDimension { + &self.objc_controller.width + } + + pub fn height(&self) -> &LayoutAnchorDimension { + &self.objc_controller.height + } +} + +impl Layout for View { + /// Returns the Objective-C object used for handling the view heirarchy. + fn get_backing_node(&self) -> Option> { + self.objc_controller.objc.clone() + } + + fn add_subview(&self, subview: &V) { + self.objc_controller.add_subview(subview); + } +} + +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/window/config.rs b/appkit/src/window/config.rs index 15d2808..b7dfc02 100644 --- a/appkit/src/window/config.rs +++ b/appkit/src/window/config.rs @@ -2,16 +2,16 @@ //! Cocoa and associated widgets. This also handles looping back //! lifecycle events, such as window resizing or close events. -use cocoa::base::{id, YES, NO}; -use cocoa::foundation::{NSRect, NSPoint, NSSize, NSUInteger}; - use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; +use crate::foundation::{id, YES, NO, NSUInteger, CGRect}; +use crate::geometry::Rect; + #[allow(non_upper_case_globals, non_snake_case)] pub mod WindowStyle { - use cocoa::foundation::NSUInteger; + use crate::foundation::NSUInteger; pub const Borderless: NSUInteger = 0; pub const Titled: NSUInteger = 1 << 0; @@ -32,7 +32,7 @@ pub struct WindowConfig(pub Id); impl Default for WindowConfig { fn default() -> Self { WindowConfig(unsafe { - let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(800., 600.)); + let dimensions: CGRect = Rect::new(0., 0., 800., 600.).into(); let style = WindowStyle::Resizable | WindowStyle::Miniaturizable | WindowStyle::UnifiedTitleAndToolbar | WindowStyle::Closable | WindowStyle::Titled; diff --git a/appkit/src/window/controller.rs b/appkit/src/window/controller.rs index 3f35b4a..5f0230e 100644 --- a/appkit/src/window/controller.rs +++ b/appkit/src/window/controller.rs @@ -4,12 +4,11 @@ use std::rc::Rc; use std::sync::Once; -use cocoa::base::id; - use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, sel, sel_impl}; +use crate::foundation::id; use crate::constants::WINDOW_CONTROLLER_PTR; use crate::utils::load; use crate::window::WindowController; diff --git a/appkit/src/window/enums.rs b/appkit/src/window/enums.rs new file mode 100644 index 0000000..822f6e9 --- /dev/null +++ b/appkit/src/window/enums.rs @@ -0,0 +1,15 @@ +//! Enums used in Window construction and handling. + +pub enum WindowTitleVisibility { + Visible, + Hidden +} + +impl From for usize { + fn from(visibility: WindowTitleVisibility) -> usize { + match visibility { + WindowTitleVisibility::Visible => 0, + WindowTitleVisibility::Hidden => 1 + } + } +} diff --git a/appkit/src/window/handle.rs b/appkit/src/window/handle.rs index e69f6d6..b2d0f1a 100644 --- a/appkit/src/window/handle.rs +++ b/appkit/src/window/handle.rs @@ -4,15 +4,12 @@ //! 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::foundation::{id, nil, YES, NO, CGSize, NSString}; use crate::layout::traits::Layout; - use crate::toolbar::{Toolbar, ToolbarController}; #[derive(Debug, Default, Clone)] @@ -24,7 +21,7 @@ impl WindowHandle { pub fn set_title(&self, title: &str) { if let Some(controller) = &self.0 { unsafe { - let title = NSString::alloc(nil).init_str(title); + let title = NSString::new(title); let window: id = msg_send![*controller, window]; let _: () = msg_send![window, setTitle:title]; } @@ -72,7 +69,7 @@ impl WindowHandle { if let Some(controller) = &self.0 { unsafe { let window: id = msg_send![*controller, window]; - let autosave = NSString::alloc(nil).init_str(name); + let autosave = NSString::new(name); let _: () = msg_send![window, setFrameAutosaveName:autosave]; } } @@ -82,7 +79,7 @@ impl WindowHandle { 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 size = CGSize::new(width.into(), height.into()); let window: id = msg_send![*controller, window]; let _: () = msg_send![window, setMinSize:size]; } diff --git a/appkit/src/window/mod.rs b/appkit/src/window/mod.rs index f7ec7f9..bb25165 100644 --- a/appkit/src/window/mod.rs +++ b/appkit/src/window/mod.rs @@ -1,9 +1,59 @@ -//! Implements wrappers and traits for `NSWindowController` and associated types. +//! Implements Window controls for macOS, by wrapping the various necessary moving pieces +//! (`NSWindow`, `NSWindowController`, and `NSWindowDelegate`) into one trait that you can +//! implement. +//! +//! For example: +//! +//! ``` +//! use appkit::prelude::{AppController, Window}; +//! use window::MyWindow; +//! +//! pub struct MyApp(Window); +//! +//! impl MyApp { +//! pub fn new() -> Self { +//! MyApp(Window::new(MyWindow { +//! // Your things here +//! })) +//! } +//! } +//! +//! impl AppController for MyApp { +//! fn did_load(&self) { +//! self.0.show(); +//! } +//! } +//! ``` +//! +//! This simulate class-based structures well enough - you just can't subclass. Now you can do the +//! following: +//! +//! ``` +//! use appkit::prelude::{WindowController, WindowHandle}; +//! +//! pub struct MyWindow; +//! +//! impl WindowController for MyWindow { +//! fn did_load(&mut self, handle: WindowHandle) {} +//! } +//! ``` -pub mod traits; -pub use traits::WindowController; +use std::rc::Rc; +use std::cell::RefCell; + +use objc::{msg_send, sel, sel_impl}; +use objc_id::ShareId; + +use crate::foundation::{id, nil}; +use crate::constants::WINDOW_CONTROLLER_PTR; +use crate::layout::traits::Layout; +use crate::toolbar::{Toolbar, ToolbarController}; mod controller; +use controller::register_window_controller_class; + +pub mod enums; +pub use enums::{WindowTitleVisibility}; pub mod config; pub use config::{WindowConfig, WindowStyle}; @@ -11,5 +61,106 @@ pub use config::{WindowConfig, WindowStyle}; pub mod handle; pub use handle::WindowHandle; -pub mod window; -pub use window::{Window, WindowTitleVisibility}; +pub mod traits; +pub use traits::WindowController; + +/// 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, Debug)] +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]`? + /// + /// This is a great question. It's because NSWindowController is... well, broken or buggy - + /// pick a term, either works. It's optimized for loading from xib/nib files, and attempting to + /// get loadWindow to fire properly is a pain in the rear (you're fighting a black box). + /// + /// This is why we do this work here, but for things subclassing `NSViewController`, we go with + /// the route of implementing `loadView`. + /// + /// APPKIT! + 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) + }; + + 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, internal_callback_ptr as usize); + + let window: id = msg_send![controller, window]; + let _: () = msg_send![window, setDelegate:controller]; + + ShareId::from_ptr(controller) + }; + + { + let mut vc = controller.borrow_mut(); + (*vc).did_load(WindowHandle(Some(inner.clone()))); + } + + Window { + internal_callback_ptr: internal_callback_ptr, + objc_controller: WindowHandle(Some(inner)), + controller: controller + } + } + + /// Sets the title for this window. + pub fn set_title(&self, title: &str) { + self.objc_controller.set_title(title.into()); + } + + /// Sets the toolbar for this window. + pub fn set_toolbar(&self, toolbar: &Toolbar) { + self.objc_controller.set_toolbar(toolbar); + } + + /// Sets the content view controller for the window. + 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) { + self.objc_controller.show(); + } + + /// Closes the window. + pub fn close(&self) { + 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); + } + } +} diff --git a/appkit/src/window/window.rs b/appkit/src/window/window.rs index 9e16329..773b913 100644 --- a/appkit/src/window/window.rs +++ b/appkit/src/window/window.rs @@ -16,23 +16,9 @@ use crate::window::handle::WindowHandle; use crate::window::traits::WindowController; use crate::window::controller::register_window_controller_class; -pub enum WindowTitleVisibility { - Visible, - Hidden -} - -impl From for usize { - fn from(visibility: WindowTitleVisibility) -> usize { - match visibility { - WindowTitleVisibility::Visible => 0, - WindowTitleVisibility::Hidden => 1 - } - } -} - /// 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)] +#[derive(Clone, Debug)] pub struct Window { internal_callback_ptr: *const RefCell, pub objc_controller: WindowHandle,