Ongoing documentation work, reworked Toolbar. Moved away from Rc/RefCell approach and will require users to handle interior mutability themselves, because Cocoa makes it tricky to automate. Beginning to figure out the split where macOS/iOS code should live.

This commit is contained in:
Ryan McGrath 2020-03-29 20:49:36 -07:00
parent 4266c4c8dc
commit 27e534a612
No known key found for this signature in database
GPG key ID: 811674B62B666830
26 changed files with 588 additions and 710 deletions

View file

@ -1,7 +1,7 @@
//! This example showcases setting up a basic application and window, and setting up some views to
//! work with autolayout.
use cacao::app::{App, AppDelegate};
use cacao::app::{App, AppDelegate, MacAppDelegate};
use cacao::color::rgb;
use cacao::layout::{Layout, LayoutConstraint};
use cacao::view::View;
@ -11,6 +11,8 @@ struct BasicApp {
window: Window<AppWindow>
}
impl MacAppDelegate for BasicApp {}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
self.window.show();

View file

@ -15,7 +15,7 @@ use objc::runtime::{Class, Object, Sel};
use url::Url;
use crate::app::APP_PTR;
use crate::app::traits::AppDelegate;
use crate::app::traits::{AppDelegate, MacAppDelegate};
use crate::error::AppKitError;
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
use crate::printing::PrintSettings;
@ -26,7 +26,7 @@ use crate::cloudkit::share::CKShareMetaData;
/// A handy method for grabbing our `AppDelegate` from the pointer. This is different from our
/// standard `utils` version as this doesn't require `RefCell` backing.
fn app<T: AppDelegate>(this: &Object) -> &T {
fn app<T>(this: &Object) -> &T {
unsafe {
let app_ptr: usize = *this.get_ivar(APP_PTR);
let app = app_ptr as *const T;
@ -45,7 +45,7 @@ extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id) {
}
/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
extern fn will_become_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn will_become_active<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_become_active();
}
@ -60,12 +60,12 @@ extern fn will_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
}
/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
extern fn did_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_resign_active<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_resign_active();
}
/// Fires when the Application Delegate receives a 'applicationShouldTerminate:` notification.
extern fn should_terminate<T: AppDelegate>(this: &Object, _: Sel, _: id) -> NSUInteger {
extern fn should_terminate<T: MacAppDelegate>(this: &Object, _: Sel, _: id) -> NSUInteger {
app::<T>(this).should_terminate().into()
}
@ -75,38 +75,38 @@ extern fn will_terminate<T: AppDelegate>(this: &Object, _: Sel, _: id) {
}
/// Fires when the Application Delegate receives a `applicationWillHide:` notification.
extern fn will_hide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn will_hide<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_hide();
}
/// Fires when the Application Delegate receives a `applicationDidHide:` notification.
extern fn did_hide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_hide<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_hide();
}
/// Fires when the Application Delegate receives a `applicationWillUnhide:` notification.
extern fn will_unhide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn will_unhide<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_unhide();
}
/// Fires when the Application Delegate receives a `applicationDidUnhide:` notification.
extern fn did_unhide<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_unhide<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_unhide();
}
/// Fires when the Application Delegate receives a `applicationWillUpdate:` notification.
extern fn will_update<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn will_update<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).will_update();
}
/// Fires when the Application Delegate receives a `applicationDidUpdate:` notification.
extern fn did_update<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_update<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_update();
}
/// Fires when the Application Delegate receives a
/// `applicationShouldHandleReopen:hasVisibleWindows:` notification.
extern fn should_handle_reopen<T: AppDelegate>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
extern fn should_handle_reopen<T: MacAppDelegate>(this: &Object, _: Sel, _: id, has_visible_windows: BOOL) -> BOOL {
match app::<T>(this).should_handle_reopen(match has_visible_windows {
YES => true,
NO => false,
@ -118,7 +118,7 @@ extern fn should_handle_reopen<T: AppDelegate>(this: &Object, _: Sel, _: id, has
}
/// Fires when the application delegate receives a `applicationDockMenu:` request.
extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
extern fn dock_menu<T: MacAppDelegate>(this: &Object, _: Sel, _: id) -> id {
match app::<T>(this).dock_menu() {
Some(mut menu) => &mut *menu.inner,
None => nil
@ -126,13 +126,13 @@ extern fn dock_menu<T: AppDelegate>(this: &Object, _: Sel, _: id) -> id {
}
/// Fires when the application delegate receives a `application:willPresentError:` notification.
extern fn will_present_error<T: AppDelegate>(this: &Object, _: Sel, _: id, error: id) -> id {
extern fn will_present_error<T: MacAppDelegate>(this: &Object, _: Sel, _: id, error: id) -> id {
let error = AppKitError::new(error);
app::<T>(this).will_present_error(error).into_nserror()
}
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
extern fn did_change_screen_parameters<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_change_screen_parameters<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).did_change_screen_parameters();
}
@ -202,7 +202,7 @@ extern fn accepted_cloudkit_share<T: AppDelegate>(_this: &Object, _: Sel, _: id,
}
/// Fires when the application receives an `application:openURLs` message.
extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
extern fn open_urls<T: MacAppDelegate>(this: &Object, _: Sel, _: id, file_urls: id) {
let urls = NSArray::wrap(file_urls).map(|url| {
let uri = NSString::wrap(unsafe {
msg_send![url, absoluteString]
@ -215,7 +215,7 @@ extern fn open_urls<T: AppDelegate>(this: &Object, _: Sel, _: id, file_urls: id)
}
/// Fires when the application receives an `application:openFileWithoutUI:` message.
extern fn open_file_without_ui<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
extern fn open_file_without_ui<T: MacAppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
let filename = NSString::wrap(file);
match app::<T>(this).open_file_without_ui(filename.to_str()) {
@ -225,7 +225,7 @@ extern fn open_file_without_ui<T: AppDelegate>(this: &Object, _: Sel, _: id, fil
}
/// Fired when the application receives an `applicationShouldOpenUntitledFile:` message.
extern fn should_open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
extern fn should_open_untitled_file<T: MacAppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
match app::<T>(this).should_open_untitled_file() {
true => YES,
false => NO
@ -233,7 +233,7 @@ extern fn should_open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id
}
/// Fired when the application receives an `applicationOpenUntitledFile:` message.
extern fn open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
extern fn open_untitled_file<T: MacAppDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
match app::<T>(this).open_untitled_file() {
true => YES,
false => NO
@ -241,7 +241,7 @@ extern fn open_untitled_file<T: AppDelegate>(this: &Object, _: Sel, _: id) -> BO
}
/// Fired when the application receives an `application:openTempFile:` message.
extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
extern fn open_temp_file<T: MacAppDelegate>(this: &Object, _: Sel, _: id, filename: id) -> BOOL {
let filename = NSString::wrap(filename);
match app::<T>(this).open_temp_file(filename.to_str()) {
@ -251,7 +251,7 @@ extern fn open_temp_file<T: AppDelegate>(this: &Object, _: Sel, _: id, filename:
}
/// Fired when the application receives an `application:printFile:` message.
extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
extern fn print_file<T: MacAppDelegate>(this: &Object, _: Sel, _: id, file: id) -> BOOL {
let filename = NSString::wrap(file);
match app::<T>(this).print_file(filename.to_str()) {
@ -262,7 +262,7 @@ extern fn print_file<T: AppDelegate>(this: &Object, _: Sel, _: id, file: id) ->
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
/// message.
extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
extern fn print_files<T: MacAppDelegate>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
let files = NSArray::wrap(files).map(|file| {
NSString::wrap(file).to_str().to_string()
});
@ -277,14 +277,14 @@ extern fn print_files<T: AppDelegate>(this: &Object, _: Sel, _: id, files: id, s
}
/// Called when the application's occlusion state has changed.
extern fn did_change_occlusion_state<T: AppDelegate>(this: &Object, _: Sel, _: id) {
extern fn did_change_occlusion_state<T: MacAppDelegate>(this: &Object, _: Sel, _: id) {
app::<T>(this).occlusion_state_changed();
}
/// Called when the application receives an `application:delegateHandlesKey:` message.
/// Note: this may not fire in sandboxed applications. Apple's documentation is unclear on the
/// matter.
extern fn delegate_handles_key<T: AppDelegate>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
extern fn delegate_handles_key<T: MacAppDelegate>(this: &Object, _: Sel, _: id, key: id) -> BOOL {
let key = NSString::wrap(key);
match app::<T>(this).delegate_handles_key(key.to_str()) {
@ -299,6 +299,52 @@ pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
decl.add_ivar::<usize>(APP_PTR);
// Launching Applications
decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching::<T> as extern fn(&Object, _, _));
decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching::<T> as extern fn(&Object, _, _));
// Managing Active Status
decl.add_method(sel!(applicationDidBecomeActive:), did_become_active::<T> as extern fn(&Object, _, _));
decl.add_method(sel!(applicationWillResignActive:), will_resign_active::<T> as extern fn(&Object, _, _));
// Terminating Applications
decl.add_method(sel!(applicationWillTerminate:), will_terminate::<T> as extern fn(&Object, _, _));
// User Activities
decl.add_method(sel!(application:willContinueUserActivityWithType:), will_continue_user_activity_with_type::<T> as extern fn(&Object, _, _, id) -> BOOL);
decl.add_method(sel!(application:continueUserActivity:restorationHandler:), continue_user_activity::<T> as extern fn(&Object, _, _, id, id) -> BOOL);
decl.add_method(sel!(application:didFailToContinueUserActivityWithType:error:), failed_to_continue_user_activity::<T> as extern fn(&Object, _, _, id, id));
decl.add_method(sel!(application:didUpdateUserActivity:), did_update_user_activity::<T> as extern fn(&Object, _, _, id));
// Handling push notifications
decl.add_method(sel!(application:didRegisterForRemoteNotificationsWithDeviceToken:), registered_for_remote_notifications::<T> as extern fn(&Object, _, _, id));
decl.add_method(sel!(application:didFailToRegisterForRemoteNotificationsWithError:), failed_to_register_for_remote_notifications::<T> as extern fn(&Object, _, _, id));
decl.add_method(sel!(application:didReceiveRemoteNotification:), did_receive_remote_notification::<T> as extern fn(&Object, _, _, id));
// CloudKit
#[cfg(feature = "cloudkit")]
decl.add_method(sel!(application:userDidAcceptCloudKitShareWithMetadata:), accepted_cloudkit_share::<T> as extern fn(&Object, _, _, id));
DELEGATE_CLASS = decl.register();
});
unsafe {
DELEGATE_CLASS
}
}
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
/// pointers we need to have.
pub(crate) fn register_mac_app_delegate_class<T: AppDelegate + MacAppDelegate>() -> *const Class {
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();

View file

@ -38,21 +38,21 @@ use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::dispatcher::Dispatcher;
use crate::foundation::{id, YES, NO, NSUInteger, AutoReleasePool};
use crate::menu::Menu;
use crate::notification_center::Dispatcher;
mod class;
use class::register_app_class;
mod delegate;
use delegate::register_app_delegate_class;
use delegate::{register_app_delegate_class, register_mac_app_delegate_class};
pub mod enums;
use enums::AppDelegateResponse;
mod enums;
pub use enums::*;
pub mod traits;
use traits::AppDelegate;
mod traits;
pub use traits::{AppDelegate, MacAppDelegate};
pub(crate) static APP_PTR: &str = "rstAppPtr";
@ -63,9 +63,38 @@ fn shared_application<F: Fn(id)>(handler: F) {
handler(app);
}
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
/// which is where our application instance lives. It also injects an `NSObject` subclass,
/// which acts as the Delegate, looping back into our Vaulthund shared application.
/// A helper method for ensuring that Cocoa is running in multi-threaded mode.
///
/// Why do we need this? According to Apple, if you're going to make use of standard POSIX threads,
/// you need to, before creating and using a POSIX thread, first create and immediately detach a
/// `NSThread`. This ensures that Cocoa utilizes proper locking in certain places where it might
/// not be doing so for performance reasons.
///
/// In general, you should aim to just start all of your work inside of your `AppDelegate` methods.
/// There are some cases where you might want to do things before that, though - and if you spawn a
/// thread there, just call this first... otherwise you may have some unexpected issues later on.
///
/// _(This is called inside the `App::new()` construct for you already, so as long as you're doing
/// nothing before your `AppDelegate`, you can pay this no mind)._
pub fn activate_cocoa_multithreading() {
unsafe {
let thread: id = msg_send![class!(NSThread), new];
let _: () = msg_send![thread, start];
}
}
/// A wrapper for `NSApplication` on macOS, and `UIApplication` on iOS.
///
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as
/// handles setting up a few necessary pieces:
///
/// - It injects an `NSObject` subclass to act as a delegate for lifecycle events.
/// - It ensures that Cocoa, where appropriate, is operating in multi-threaded mode so POSIX
/// threads work as intended.
///
/// This also enables support for dispatching a message, `M`. Your `AppDelegate` can optionally
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
/// application.
pub struct App<T = (), M = ()> {
pub inner: Id<Object>,
pub objc_delegate: Id<Object>,
@ -74,13 +103,30 @@ pub struct App<T = (), M = ()> {
_t: std::marker::PhantomData<M>
}
impl<T> App<T> where T: AppDelegate + 'static {
impl<T> App<T> {
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
/// If you're wondering where to go from here... you need an `AppDelegate` that implements
/// `did_finish_launching`. :)
pub fn run(&self) {
unsafe {
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let _: () = msg_send![current_app, activateWithOptions:1<<1];
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
}
}
}
impl<T> App<T> where T: AppDelegate + MacAppDelegate + 'static {
/// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
/// Objective-C side of things.
pub fn new(_bundle_id: &str, delegate: T) -> Self {
// set_bundle_id(bundle_id);
activate_cocoa_multithreading();
let pool = AutoReleasePool::new();
let inner = unsafe {
@ -92,7 +138,7 @@ impl<T> App<T> where T: AppDelegate + 'static {
let app_delegate = Box::new(delegate);
let objc_delegate = unsafe {
let delegate_class = register_app_delegate_class::<T>();
let delegate_class = register_mac_app_delegate_class::<T>();
let delegate: id = msg_send![delegate_class, new];
let delegate_ptr: *const T = &*app_delegate;
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
@ -108,19 +154,6 @@ impl<T> App<T> where T: AppDelegate + 'static {
_t: std::marker::PhantomData
}
}
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
/// If you're wondering where to go from here... you need an `AppDelegate` that implements
/// `did_finish_launching`. :)
pub fn run(&self) {
unsafe {
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let _: () = msg_send![current_app, activateWithOptions:1<<1];
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
}
}
}
// This is a hack and should be replaced with an actual messaging pipeline at some point. :)

View file

@ -27,15 +27,68 @@ pub trait AppDelegate {
/// through to your shared application, then used the `App::shared()` call.
fn did_finish_launching(&self) {}
/// Fired immediately before the application is about to become active.
fn will_become_active(&self) {}
/// Fired when the application is about to become active.
fn did_become_active(&self) {}
/// Fired when the application is about to resign active state.
fn will_resign_active(&self) {}
/// Fired when the user is going to continue an activity.
fn will_continue_user_activity(&self, _activity_type: &str) -> bool { false }
/// Fired when data for continuing an activity is available. Currently, the
/// `restoration_handler` is not used, but there to communicate intent with what this API will
/// eventually be doing.
fn continue_user_activity<F: Fn()>(&self, _activity: UserActivity, _restoration_handler: F) -> bool { false }
/// Fired when the activity could not be continued.
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: AppKitError) {}
/// Fired after the user activity object has been updated.
fn updated_user_activity(&self, _activity: UserActivity) {}
/// Fired when you've successfully registered for remote notifications with APNS.
fn registered_for_remote_notifications(&self, _token: &str) {}
/// Fired after you've received a push notification from APNS.
//fn did_receive_remote_notification(&self, notification: PushNotification) {}
/// Fired if there was a failure to register for remote notifications with APNS - e.g,
/// connection issues or something.
fn failed_to_register_for_remote_notifications(&self, _error: AppKitError) {}
/// Fires after the user accepted a CloudKit sharing invitation associated with your
/// application.
#[cfg(feature = "cloudkit")]
fn user_accepted_cloudkit_share(&self, _share_metadata: CKShareMetaData) {}
/// Fired before the application terminates. You can use this to do any required cleanup.
fn will_terminate(&self) {}
}
/// `SceneDelegate` maps over to the newer iOS13+ API. This is necessary in order to support
/// multiple windows (scenes) on iPadOS, which is a desirable feature.
pub trait SceneDelegate {
/*fn configuration_for(
&mut self,
session: SceneSession,
options: &[SceneConnectionOptions]
) -> SceneConfiguration {
}
fn did_discard(&mut self, sessions: &[SceneSession]) {}
*/
}
pub trait IOSAppDelegate {
}
pub trait MacAppDelegate {
/// Fired immediately before the application is about to become active.
fn will_become_active(&self) {}
/// Fired when the application has resigned active state.
fn did_resign_active(&self) {}
@ -65,9 +118,6 @@ pub trait AppDelegate {
/// back.
fn should_terminate(&self) -> TerminateResponse { TerminateResponse::Now }
/// Fired before the application terminates. You can use this to do any required cleanup.
fn will_terminate(&self) {}
/// Sent by the application to the delegate prior to default behavior to reopen AppleEvents.
///
/// `has_visible_windows` indicates whether the Application object found any visible windows in your application.
@ -100,37 +150,6 @@ pub trait AppDelegate {
/// something in their settings).
fn did_change_screen_parameters(&self) {}
/// Fired when the user is going to continue an activity.
fn will_continue_user_activity(&self, _activity_type: &str) -> bool { false }
/// Fired when data for continuing an activity is available. Currently, the
/// `restoration_handler` is not used, but there to communicate intent with what this API will
/// eventually be doing.
fn continue_user_activity<F: Fn()>(&self, _activity: UserActivity, _restoration_handler: F) -> bool { false }
/// Fired when the activity could not be continued.
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: AppKitError) {}
/// Fired after the user activity object has been updated.
fn updated_user_activity(&self, _activity: UserActivity) {}
/// Fired when you've successfully registered for remote notifications with APNS.
fn registered_for_remote_notifications(&self, _token: &str) {
}
/// Fired after you've received a push notification from APNS.
//fn did_receive_remote_notification(&self, notification: PushNotification) {}
/// Fired if there was a failure to register for remote notifications with APNS - e.g,
/// connection issues or something.
fn failed_to_register_for_remote_notifications(&self, _error: AppKitError) {}
/// Fires after the user accepted a CloudKit sharing invitation associated with your
/// application.
#[cfg(feature = "cloudkit")]
fn user_accepted_cloudkit_share(&self, _share_metadata: CKShareMetaData) {}
/// Fired when you have a list of `Url`'s to open. This is best explained by quoting the Apple
/// documentation verbatim:
///

View file

@ -13,7 +13,7 @@ use crate::foundation::{nil, NSString};
/// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSButton` lives.
pub struct Button {
pub inner: Id<Object>
pub objc: Id<Object>
}
impl Button {
@ -21,19 +21,19 @@ impl Button {
/// and retains the necessary Objective-C runtime pointer.
pub fn new(text: &str) -> Self {
let title = NSString::new(text);
let inner = unsafe {
let objc = unsafe {
Id::from_ptr(msg_send![register_class(), buttonWithTitle:title target:nil action:nil])
};
Button {
inner: inner
objc: objc
}
}
/// Sets the bezel style for this button.
pub fn set_bezel_style(&self, bezel_style: i32) {
unsafe {
let _: () = msg_send![&*self.inner, setBezelStyle:bezel_style];
let _: () = msg_send![&*self.objc, setBezelStyle:bezel_style];
}
}
}

View file

@ -1,4 +0,0 @@
//! Constants typically used for referencing around in the Objective-C runtime.
//! Specific to this crate.
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";

View file

@ -80,8 +80,6 @@ pub mod button;
pub mod cloudkit;
pub mod color;
pub mod constants;
pub mod dispatcher;
pub mod dragdrop;
pub mod error;
pub mod events;
@ -91,20 +89,20 @@ pub mod geometry;
pub mod layout;
pub mod menu;
pub mod networking;
pub mod notification_center;
#[cfg(feature = "user-notifications")]
pub mod notifications;
pub mod user_notifications;
pub mod pasteboard;
pub mod printing;
pub mod toolbar;
pub mod user_activity;
pub mod utils;
pub mod view;
pub mod user_defaults;
//pub mod view;
#[cfg(feature = "webview")]
pub mod webview;
pub mod window;
pub mod prelude;

View file

@ -0,0 +1,52 @@
//! A wrapper for `NSNotificationCenter`.
//!
//! With this, you can:
//!
//! - Register for notifications, both from the system or posted from your code
//! - Post your own notifications
//! - Clean up and remove your handlers
//!
//! Note that in some cases (e.g, looping) this will be much slower than if you have a handle and
//! can call through to your desired path directly. This control is provided due to the need for
//! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks.
//!
//! ## Example
//! ```rust,no_run
//!
//! ```
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
mod traits;
pub use traits::Dispatcher;
/// Wraps a reference to an `NSNotificationCenter` instance. Currently this only supports the
/// default center; in the future it should aim to support custom variants.
#[derive(Debug)]
pub struct NotificationCenter(pub ShareId<Object>);
impl Default for NotificationCenter {
/// Returns a wrapper over `[NSNotificationCenter defaultCenter]`. From here you can handle
/// observing, removing, and posting notifications.
fn default() -> Self {
NotificationCenter(unsafe {
ShareId::from_ptr(msg_send![class!(NSNotificationCenter), defaultCenter])
})
}
}
impl NotificationCenter {
pub fn observe<T: Dispatcher>(&self, name: &str, handler: &T) {
}
pub fn remove<T: Dispatcher>(&self, name: &str, handler: &T) {
}
pub fn post(&self, name: &str) {
}
}

View file

@ -1,29 +0,0 @@
//! The prelude imports a large amount of useful widgets and traits. You're of course free to
//! thread imports yourself, but for many smaller applications this module can be quite a time
//! saver.
pub use crate::app::{App, traits::AppDelegate};
pub use crate::dispatcher::Dispatcher;
pub use crate::layout::LayoutConstraint;
pub use crate::menu::{Menu, MenuItem};
#[cfg(feature = "user-notifications")]
pub use crate::notifications::{Notification, NotificationCenter, NotificationAuthOption};
pub use crate::toolbar::{Toolbar, ToolbarController, ToolbarHandle};
pub use crate::networking::URLRequest;
pub use crate::window::{
Window, config::WindowConfig, traits::WindowDelegate
};
#[cfg(feature = "webview")]
pub use crate::webview::{
WebView, WebViewConfig, WebViewDelegate
};
pub use crate::view::{View, traits::ViewDelegate};

View file

@ -1,6 +1,5 @@
//! Handles the Objective-C functionality for the Toolbar module.
use std::rc::Rc;
use std::sync::Once;
use objc::declare::ClassDecl;
@ -8,60 +7,45 @@ use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::foundation::{id, NSArray, NSString};
use crate::constants::TOOLBAR_PTR;
use crate::toolbar::traits::ToolbarController;
use crate::toolbar::{TOOLBAR_PTR, ToolbarDelegate};
use crate::utils::load;
/// Retrieves and passes the allowed item identifiers for this toolbar.
extern fn allowed_item_identifiers<T: ToolbarController>(this: &Object, _: Sel, _: id) -> id {
extern fn allowed_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
println!("Hitting here?");
let identifiers: NSArray = {
let t = toolbar.borrow();
(*t).allowed_item_identifiers().iter().map(|identifier| {
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
NSString::new(identifier).into_inner()
}).collect::<Vec<id>>().into()
};
}).collect::<Vec<id>>().into();
Rc::into_raw(toolbar);
identifiers.into_inner()
}
/// Retrieves and passes the default item identifiers for this toolbar.
extern fn default_item_identifiers<T: ToolbarController>(this: &Object, _: Sel, _: id) -> id {
extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
let identifiers: NSArray = {
let t = toolbar.borrow();
(*t).default_item_identifiers().iter().map(|identifier| {
let identifiers: NSArray = toolbar.default_item_identifiers().iter().map(|identifier| {
NSString::new(identifier).into_inner()
}).collect::<Vec<id>>().into()
};
}).collect::<Vec<id>>().into();
Rc::into_raw(toolbar);
identifiers.into_inner()
}
/// Loads the controller, grabs whatever item is for this identifier, and returns what the
/// Objective-C runtime needs.
extern fn item_for_identifier<T: ToolbarController>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR);
let identifier = NSString::wrap(identifier).to_str();
let mut item = {
let t = toolbar.borrow();
let item = (*t).item_for(identifier);
item
};
Rc::into_raw(toolbar);
&mut *item.inner
let mut item = toolbar.item_for(identifier);
&mut *item.objc
}
/// Registers a `NSToolbar` subclass, and configures it to hold some ivars for various things we need
/// to store. We use it as our delegate as well, just to cut down on moving pieces.
pub(crate) fn register_toolbar_class<T: ToolbarController>() -> *const Class {
pub(crate) fn register_toolbar_class<T: ToolbarDelegate>() -> *const Class {
static mut TOOLBAR_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();

View file

@ -1,53 +0,0 @@
//! A wrapper for the underlying `NSToolbar`, which is safe to clone and pass around. We do this to
//! provide a uniform and expectable API.
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{YES, NO, NSUInteger};
use crate::toolbar::types::{ToolbarDisplayMode, ToolbarSizeMode};
#[derive(Clone, Debug)]
pub struct ToolbarHandle(pub ShareId<Object>);
impl ToolbarHandle {
/// Indicates whether the toolbar shows the separator between the toolbar and the main window
/// contents.
pub fn set_shows_baseline_separator(&self, shows: bool) {
unsafe {
let _: () = msg_send![&*self.0, setShowsBaselineSeparator:match shows {
true => YES,
false => NO
}];
}
}
/// Sets the toolbar's display mode.
pub fn set_display_mode(&self, mode: ToolbarDisplayMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.0, setDisplayMode:mode];
}
}
/// Sets the toolbar's size mode.
pub fn set_size_mode(&self, mode: ToolbarSizeMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.0, setSizeMode:mode];
}
}
/// Set whether the toolbar is visible or not.
pub fn set_visible(&self, visibility: bool) {
unsafe {
let _: () = msg_send![&*self.0, setVisible:match visibility {
true => YES,
false => NO
}];
}
}
}

View file

@ -12,22 +12,20 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NSString};
use crate::button::Button;
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSWindow` and associated delegate live.
/// Wraps `NSToolbarItem`. Enables configuring things like size, view, and so on.
pub struct ToolbarItem {
pub identifier: String,
pub inner: Id<Object>,
pub objc: Id<Object>,
pub button: Option<Button>
}
impl ToolbarItem {
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
/// pointers.
/// Creates and returns a new `ToolbarItem`, ensuring the underlying `NSToolbarItem` is
/// properly initialized.
pub fn new<S: Into<String>>(identifier: S) -> Self {
let identifier = identifier.into();
let inner = unsafe {
let objc = unsafe {
let identifr = NSString::new(&identifier);
let alloc: id = msg_send![class!(NSToolbarItem), alloc];
let item: id = msg_send![alloc, initWithItemIdentifier:identifr];
@ -36,7 +34,7 @@ impl ToolbarItem {
ToolbarItem {
identifier: identifier,
inner: inner,
objc: objc,
button: None
}
}
@ -45,7 +43,7 @@ impl ToolbarItem {
pub fn set_title(&mut self, title: &str) {
unsafe {
let title = NSString::new(title);
let _: () = msg_send![&*self.inner, setTitle:title];
let _: () = msg_send![&*self.objc, setTitle:title];
}
}
@ -54,7 +52,7 @@ impl ToolbarItem {
button.set_bezel_style(11);
unsafe {
let _: () = msg_send![&*self.inner, setView:&*button.inner];
let _: () = msg_send![&*self.objc, setView:&*button.objc];
}
self.button = Some(button);
@ -64,7 +62,7 @@ impl ToolbarItem {
pub fn set_min_size(&mut self, width: f64, height: f64) {
unsafe {
let size = CGSize::new(width.into(), height.into());
let _: () = msg_send![&*self.inner, setMinSize:size];
let _: () = msg_send![&*self.objc, setMinSize:size];
}
}
@ -72,7 +70,7 @@ impl ToolbarItem {
pub fn set_max_size(&mut self, width: f64, height: f64) {
unsafe {
let size = CGSize::new(width.into(), height.into());
let _: () = msg_send![&*self.inner, setMaxSize:size];
let _: () = msg_send![&*self.objc, setMaxSize:size];
}
}
}

View file

@ -1,48 +1,124 @@
//! This module contains a wrapper for `NSToolbar`, one of the standard UI elements in a native
//! Cocoa application. To customize it and provide options, you should implement a
//! `ToolbarController` for your desired struct, and instantiate your `Toolbar` with it. For
//! example:
//! Implements an NSToolbar, which is one of those macOS niceties
//! that makes it feel... "proper".
//!
//! ```
//! use cacao::prelude::*;
//!
//! #[derive(Default)]
//! struct WindowToolbar;
//!
//! impl ToolbarController for WindowToolbar {
//! /* Your trait implementation here */
//! }
//!
//! ```
//!
//! And then, wherever your window is:
//!
//! ```
//! #[derive(Default)]
//! struct AppWindow {
//! pub toolbar: Toolbar<WindowToolbar>
//! }
//!
//! impl WindowController for AppWindow {
//! fn did_load(&mut self, window: WindowHandle) {
//! window.set_toolbar(&self.toolbar);
//! }
//! }
//! ```
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
pub(crate) mod class;
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
pub mod handle;
pub use handle::ToolbarHandle;
use crate::foundation::{id, nil, YES, NO, NSString, NSUInteger};
pub mod item;
mod class;
use class::register_toolbar_class;
mod item;
pub use item::ToolbarItem;
pub mod traits;
pub use traits::ToolbarController;
mod traits;
pub use traits::ToolbarDelegate;
pub mod toolbar;
pub use toolbar::Toolbar;
mod enums;
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode};
pub mod types;
pub use types::*;
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSToolbar` and associated delegate live.
pub struct Toolbar<T = ()> {
/// An internal identifier used by the toolbar. We cache it here in case users want it.
pub identifier: String,
/// The Objective-C runtime toolbar.
pub objc: ShareId<Object>,
/// The user supplied delegate.
pub delegate: Option<Box<T>>
}
impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
/// Creates a new `NSToolbar` instance, configures it appropriately, sets up the delegate
/// chain, and retains it all.
pub fn new<S: Into<String>>(identifier: S, delegate: T) -> Self {
let identifier = identifier.into();
let delegate = Box::new(delegate);
let objc = unsafe {
let cls = register_toolbar_class::<T>();
let alloc: id = msg_send![cls, alloc];
let identifier = NSString::new(&identifier);
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
let ptr: *const T = &*delegate;
(&mut *toolbar).set_ivar(TOOLBAR_PTR, ptr as usize);
let _: () = msg_send![toolbar, setDelegate:toolbar];
ShareId::from_ptr(toolbar)
};
&delegate.did_load(Toolbar {
objc: objc.clone(),
identifier: identifier.clone(),
delegate: None
});
Toolbar {
identifier: identifier,
objc: objc,
delegate: Some(delegate),
}
}
}
impl<T> Toolbar<T> {
/// Indicates whether the toolbar shows the separator between the toolbar and the main window
/// contents.
pub fn set_shows_baseline_separator(&self, shows: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setShowsBaselineSeparator:match shows {
true => YES,
false => NO
}];
}
}
/// Sets the toolbar's display mode.
pub fn set_display_mode(&self, mode: ToolbarDisplayMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.objc, setDisplayMode:mode];
}
}
/// Sets the toolbar's size mode.
pub fn set_size_mode(&self, mode: ToolbarSizeMode) {
let mode: NSUInteger = mode.into();
unsafe {
let _: () = msg_send![&*self.objc, setSizeMode:mode];
}
}
/// Set whether the toolbar is visible or not.
pub fn set_visible(&self, visibility: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setVisible:match visibility {
true => YES,
false => NO
}];
}
}
}
impl<T> Drop for Toolbar<T> {
/// A bit of extra cleanup for the delegate system. If we have a non-`None` delegate, this is
/// the OG Toolbar and should be cleaned up for any possible cyclical references.
fn drop(&mut self) {
if self.delegate.is_some() {
unsafe {
let _: () = msg_send![&*self.objc, setDelegate:nil];
}
}
}
}

View file

@ -1,105 +0,0 @@
//! Implements an NSToolbar, which is one of those macOS niceties
//! that makes it feel... "proper".
//!
//! UNFORTUNATELY, this is a very old and janky API. So... yeah.
use std::cell::RefCell;
use std::rc::Rc;
use objc_id::ShareId;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, NSString};
use crate::constants::TOOLBAR_PTR;
use crate::toolbar::class::register_toolbar_class;
use crate::toolbar::handle::ToolbarHandle;
use crate::toolbar::traits::ToolbarController;
use crate::toolbar::types::{ToolbarDisplayMode, ToolbarSizeMode};
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSToolbar` and associated delegate live.
pub struct Toolbar<T> {
/// A pointer that we "forget" until dropping this struct. This allows us to keep the retain
/// count of things appropriate until the Toolbar is done.
internal_callback_ptr: *const RefCell<T>,
/// An internal identifier used by the toolbar. We cache it here in case users want it.
pub identifier: String,
/// The Objective-C runtime controller (the toolbar, really - it does double duty).
pub objc_controller: ToolbarHandle,
/// The user supplied controller.
pub controller: Rc<RefCell<T>>
}
impl<T> Toolbar<T> where T: ToolbarController + 'static {
/// Creates a new `NSToolbar` instance, configures it appropriately, injects an `NSObject`
/// delegate wrapper, and retains the necessary Objective-C runtime pointers.
pub fn new<S: Into<String>>(identifier: S, controller: T) -> Self {
let identifier = identifier.into();
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let objc_controller = unsafe {
let delegate_class = register_toolbar_class::<T>();
let identifier = NSString::new(&identifier);
let alloc: id = msg_send![delegate_class, alloc];
let toolbar: id = msg_send![alloc, initWithIdentifier:identifier];
(&mut *toolbar).set_ivar(TOOLBAR_PTR, internal_callback_ptr as usize);
let _: () = msg_send![toolbar, setDelegate:toolbar];
ShareId::from_ptr(toolbar)
};
{
let mut c = controller.borrow_mut();
(*c).did_load(ToolbarHandle(objc_controller.clone()));
}
Toolbar {
internal_callback_ptr: internal_callback_ptr,
identifier: identifier,
objc_controller: ToolbarHandle(objc_controller),
controller: controller
}
}
/// Indicates whether the toolbar shows the separator between the toolbar and the main window
/// contents.
pub fn set_shows_baseline_separator(&self, shows: bool) {
self.objc_controller.set_shows_baseline_separator(shows);
}
/// Sets the toolbar's display mode.
pub fn set_display_mode(&self, mode: ToolbarDisplayMode) {
self.objc_controller.set_display_mode(mode);
}
/// Sets the toolbar's size mode.
pub fn set_size_mode(&self, mode: ToolbarSizeMode) {
self.objc_controller.set_size_mode(mode);
}
/// Set whether the toolbar is visible or not.
pub fn set_visible(&self, visibility: bool) {
self.objc_controller.set_visible(visibility);
}
}
impl<T> Drop for Toolbar<T> {
/// A bit of extra cleanup for delegate callback pointers.
/// Note: this currently doesn't check to see if it needs to be removed from a Window it's
/// attached to. In theory this is fine... in practice (and in Rust) it might be wonky, so
/// worth circling back on at some point.
fn drop(&mut self) {
unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -2,15 +2,14 @@
//! go. Currently a bit incomplete in that we don't support the customizing workflow, but feel free
//! to pull request it.
use crate::toolbar::handle::ToolbarHandle;
use crate::toolbar::item::ToolbarItem;
use crate::toolbar::{Toolbar, ToolbarItem};
/// A trait that you can implement to have your struct/etc act as an `NSToolbarDelegate`.
pub trait ToolbarController {
pub trait ToolbarDelegate {
/// This method can be used to configure your toolbar, if you need to do things involving the
/// handle. Unlike some other view types, it's not strictly necessary, and is provided in the
/// interest of a uniform and expectable API.
fn did_load(&mut self, _toolbar: ToolbarHandle) {}
fn did_load(&self, _toolbar: Toolbar) {}
/// What items are allowed in this toolbar.
fn allowed_item_identifiers(&self) -> Vec<&'static str>;

52
src/user_defaults.rs Normal file
View file

@ -0,0 +1,52 @@
//! Wraps `NSUserDefaults`, providing an interface to store and query small amounts of data.
//!
//! It mirrors much of the API of the standard Rust `HashMap`, but uses `NSUserDefaults` as a
//! backing store.
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::Id;
use crate::foundation::{id, NSString};
#[derive(Debug)]
pub struct UserDefaults(pub Id<Object>);
impl Default for UserDefaults {
fn default() -> Self {
UserDefaults::standard()
}
}
impl UserDefaults {
pub fn standard() -> Self {
UserDefaults(unsafe {
Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults])
})
}
pub fn new() -> Self {
UserDefaults(unsafe {
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
Id::from_ptr(msg_send![alloc, init])
})
}
pub fn suite(named: &str) -> Self {
let name = NSString::new(named);
UserDefaults(unsafe {
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()])
})
}
/// Remove the default associated with the key. If the key doesn't exist, this is a noop.
pub fn remove(&self, key: &str) {
let key = NSString::new(key);
unsafe {
let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()];
}
}
}

View file

@ -2,9 +2,6 @@
//! belong to. These are typically internal, and if you rely on them... well, don't be surprised if
//! they go away one day.
use std::rc::Rc;
use std::cell::RefCell;
use core_graphics::base::CGFloat;
use objc::{Encode, Encoding};
@ -17,16 +14,31 @@ pub trait Controller {
fn get_backing_node(&self) -> ShareId<Object>;
}
/// Used for moving a pointer back into an Rc, so we can work with the object held behind it. Note
/// that it's very important to make sure you reverse this when you're done (using
/// `Rc::into_raw()`) otherwise you'll cause problems due to the `Drop` logic.
pub fn load<T>(this: &Object, ptr: &str) -> Rc<RefCell<T>> {
/// Utility method for taking a pointer and grabbing the corresponding delegate in Rust. This is
/// theoretically safe:
///
/// - The object (`this`) is owned by the wrapping component (e.g, a `Window`). It's released when
/// the `Window` is released.
/// - The only other place where you can retrieve a `Window` (or such control) is in the respective
/// delegate `did_load()` method, where you're passed one. This variant never includes the
/// delegate.
/// - Thus, provided the root object still exists, this pointer should be valid (root objects Box
/// them, so they ain't movin').
/// - The way this _could_ fail would be if the programmer decides to clone their `Window` or such
/// object deeper into the stack (or elsewhere in general). This is why we don't allow them to be
/// cloned, though.
///
/// This is, like much in this framework, subject to revision pending more thorough testing and
/// checking.
pub fn load<'a, T>(this: &'a Object, ptr_name: &str) -> &'a T {
unsafe {
let ptr: usize = *this.get_ivar(ptr);
let view_ptr = ptr as *const RefCell<T>;
Rc::from_raw(view_ptr)
let ptr: usize = *this.get_ivar(ptr_name);
let obj = ptr as *const T;
&*obj
}
}
/// Upstream core graphics does not implement Encode for certain things, so we wrap them here -
/// these are only used in reading certain types passed to us from some delegate methods.
#[repr(C)]

View file

@ -1,7 +1,6 @@
//! Everything useful for the `WindowDelegate`. Handles injecting an `NSWindowDelegate` subclass
//! into the Objective C runtime, which loops back to give us lifecycle methods.
use std::rc::Rc;
use std::sync::Once;
use core_graphics::base::CGFloat;
@ -19,14 +18,7 @@ use crate::window::{WindowDelegate, WINDOW_DELEGATE_PTR};
extern fn should_close<T: WindowDelegate>(this: &Object, _: Sel, _: id) -> BOOL {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
let result = {
let window = window.borrow();
(*window).should_close()
};
Rc::into_raw(window);
match result {
match window.should_close() {
true => YES,
false => NO
}
@ -36,185 +28,97 @@ extern fn should_close<T: WindowDelegate>(this: &Object, _: Sel, _: id) -> BOOL
/// Good place to clean up memory and what not.
extern fn will_close<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_close();
}
Rc::into_raw(window);
window.will_close();
}
/// Called when an `NSWindowDelegate` receives a `windowWillMove:` event.
extern fn will_move<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_move();
}
Rc::into_raw(window);
window.will_move();
}
/// Called when an `NSWindowDelegate` receives a `windowDidMove:` event.
extern fn did_move<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_move();
}
Rc::into_raw(window);
window.did_move();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn did_change_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_change_screen();
}
Rc::into_raw(window);
window.did_change_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn did_change_screen_profile<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_change_screen_profile();
}
Rc::into_raw(window);
window.did_change_screen_profile();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn will_resize<T: WindowDelegate>(this: &Object, _: Sel, _: id, size: CGSize) -> CGSize {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
let result = {
let window = window.borrow();
let s = (*window).will_resize(size.width as f64, size.height as f64);
let s = window.will_resize(size.width as f64, size.height as f64);
CGSize {
width: s.0 as CGFloat,
height: s.1 as CGFloat
}
};
Rc::into_raw(window);
result
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn did_resize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_resize();
}
Rc::into_raw(window);
window.did_resize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn will_start_live_resize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_start_live_resize();
}
Rc::into_raw(window);
window.will_start_live_resize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn did_end_live_resize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_end_live_resize();
}
Rc::into_raw(window);
window.did_end_live_resize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn will_miniaturize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_miniaturize();
}
Rc::into_raw(window);
window.will_miniaturize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn did_miniaturize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_miniaturize();
}
Rc::into_raw(window);
window.did_miniaturize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreen:` event.
extern fn did_deminiaturize<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_deminiaturize();
}
Rc::into_raw(window);
window.did_deminiaturize();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn will_enter_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_enter_full_screen();
}
Rc::into_raw(window);
window.will_enter_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn did_enter_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_enter_full_screen();
}
Rc::into_raw(window);
window.did_enter_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn content_size_for_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id, size: CGSize) -> CGSize {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
let result = {
let window = window.borrow();
let (width, height) = (*window).content_size_for_full_screen(
let (width, height) = window.content_size_for_full_screen(
size.width as f64,
size.height as f64
);
@ -223,186 +127,98 @@ extern fn content_size_for_full_screen<T: WindowDelegate>(this: &Object, _: Sel,
width: width as CGFloat,
height: height as CGFloat
}
};
Rc::into_raw(window);
result
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn options_for_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id, options: NSUInteger) -> NSUInteger {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
let result = {
let window = window.borrow();
let options = (*window).presentation_options_for_full_screen();
let desired_opts = window.presentation_options_for_full_screen();
if options.is_none() {
None
if desired_opts.is_none() {
options
} else {
let mut opts: NSUInteger = 0;
for opt in options.unwrap() {
for opt in desired_opts.unwrap() {
opts = opts << NSUInteger::from(opt);
}
Some(opts)
}
};
Rc::into_raw(window);
match result {
Some(options) => options,
None => options
opts
}
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn will_exit_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).will_exit_full_screen();
}
Rc::into_raw(window);
window.will_exit_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn did_exit_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_exit_full_screen();
}
Rc::into_raw(window);
window.did_exit_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn did_fail_to_enter_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_fail_to_enter_full_screen();
}
Rc::into_raw(window);
window.did_fail_to_enter_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeScreenProfile:` event.
extern fn did_fail_to_exit_full_screen<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_fail_to_exit_full_screen();
}
Rc::into_raw(window);
window.did_fail_to_exit_full_screen();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeBackingProperties:` event.
extern fn did_change_backing_properties<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_change_backing_properties();
}
Rc::into_raw(window);
window.did_change_backing_properties();
}
/// Called when an `NSWindowDelegate` receives a `windowDidChangeBackingProperties:` event.
extern fn did_change_occlusion_state<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_change_occlusion_state();
}
Rc::into_raw(window);
window.did_change_occlusion_state();
}
/// Called when an `NSWindowDelegate` receives a `windowDidUpdate:` event.
extern fn did_update<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_update();
}
Rc::into_raw(window);
window.did_update();
}
/// Called when an `NSWindowDelegate` receives a `windowDidExpose:` event.
extern fn did_become_main<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_become_main();
}
Rc::into_raw(window);
window.did_become_main();
}
/// Called when an `NSWindowDelegate` receives a `windowDidExpose:` event.
extern fn did_resign_main<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_resign_main();
}
Rc::into_raw(window);
window.did_resign_main();
}
/// Called when an `NSWindowDelegate` receives a `windowDidExpose:` event.
extern fn did_become_key<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_become_key();
}
Rc::into_raw(window);
window.did_become_key();
}
/// Called when an `NSWindowDelegate` receives a `windowDidExpose:` event.
extern fn did_resign_key<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_resign_key();
}
Rc::into_raw(window);
window.did_resign_key();
}
/// Called when an `NSWindowDelegate` receives a `windowDidExpose:` event.
extern fn did_expose<T: WindowDelegate>(this: &Object, _: Sel, _: id) {
let window = load::<T>(this, WINDOW_DELEGATE_PTR);
{
let window = window.borrow();
(*window).did_expose();
window.did_expose();
}
Rc::into_raw(window);
}
/// Injects an `NSWindow` subclass, with some callback and pointer ivars for what we
/// need to do.
pub(crate) fn register_window_class() -> *const Class {

View file

@ -52,25 +52,23 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
/// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied delegate.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let mut window = Window::with(config, delegate);
let window = Window::with(config, delegate);
let objc = 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.objc];
if let Some(ptr) = window.internal_callback_ptr {
if let Some(delegate) = &window.delegate {
let ptr: *const T = &**delegate;
(&mut *controller).set_ivar(WINDOW_DELEGATE_PTR, ptr as usize);
}
ShareId::from_ptr(controller)
};
if let Some(window_delegate) = &mut window.delegate {
let mut window_delegate = window_delegate.borrow_mut();
(*window_delegate).did_load(Window {
internal_callback_ptr: None,
if let Some(delegate) = &window.delegate {
(*delegate).did_load(Window {
delegate: None,
objc: window.objc.clone()
});

View file

@ -9,8 +9,6 @@
//! the `objc` field on a `Window` to instrument it with the Objective-C runtime on your own.
use std::unreachable;
use std::rc::Rc;
use std::cell::RefCell;
use core_graphics::base::CGFloat;
use core_graphics::geometry::{CGRect, CGSize};
@ -22,22 +20,23 @@ use objc_id::ShareId;
use crate::color::Color;
use crate::foundation::{id, nil, YES, NO, NSString, NSInteger, NSUInteger};
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
use crate::toolbar::{Toolbar, ToolbarDelegate};
use crate::utils::Controller;
mod class;
use class::{register_window_class, register_window_class_with_delegate};
pub mod config;
use config::WindowConfig;
mod config;
pub use config::WindowConfig;
pub mod controller;
mod controller;
pub use controller::WindowController;
pub mod enums;
use enums::TitleVisibility;
mod enums;
pub use enums::*;
pub mod traits;
use traits::WindowDelegate;
mod traits;
pub use traits::WindowDelegate;
pub(crate) static WINDOW_DELEGATE_PTR: &str = "rstWindowDelegate";
@ -45,14 +44,11 @@ pub(crate) static WINDOW_DELEGATE_PTR: &str = "rstWindowDelegate";
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Debug)]
pub struct Window<T = ()> {
/// A pointer to the Objective-C `NS/UIWindow`. Used in callback orchestration.
pub(crate) internal_callback_ptr: Option<*const RefCell<T>>,
/// Represents an `NS/UIWindow` in the Objective-C runtime.
pub objc: ShareId<Object>,
/// A delegate for this window.
pub delegate: Option<Rc<RefCell<T>>>
pub delegate: Option<Box<T>>
}
impl Default for Window {
@ -97,13 +93,68 @@ impl Window {
};
Window {
internal_callback_ptr: None,
objc: objc,
delegate: None
}
}
}
impl<T> Window<T> where T: WindowDelegate + 'static {
/// Constructs a new Window with a `config` and `delegate`. Using a `WindowDelegate` enables
/// you to respond to window lifecycle events - visibility, movement, and so on. It also
/// enables easier structure of your codebase, and in a way simulates traditional class based
/// architectures... just without the subclassing.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let delegate = Box::new(delegate);
let objc = unsafe {
let alloc: id = msg_send![register_window_class_with_delegate::<T>(), alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
let buffered: NSUInteger = 2;
let dimensions: CGRect = config.initial_dimensions.into();
let window: id = msg_send![alloc, initWithContentRect:dimensions
styleMask:config.style
backing:buffered
defer:match config.defer {
true => YES,
false => NO
}
];
let delegate_ptr: *const T = &*delegate;
(&mut *window).set_ivar(WINDOW_DELEGATE_PTR, delegate_ptr as usize);
let _: () = msg_send![window, autorelease];
// This is very important! NSWindow is an old class and has some behavior that we need
// to disable, like... this. If we don't set this, we'll segfault entirely because the
// Objective-C runtime gets out of sync by releasing the window out from underneath of
// us.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
// We set the window to be its own delegate - this is cleaned up inside `Drop`.
let _: () = msg_send![window, setDelegate:window];
ShareId::from_ptr(window)
};
{
&delegate.did_load(Window {
delegate: None,
objc: objc.clone()
});
}
Window {
objc: objc,
delegate: Some(delegate)
}
}
}
impl<T> Window<T> {
/// Handles setting the title on the underlying window. Allocates and passes an `NSString` over
/// to the Objective C runtime.
@ -167,9 +218,9 @@ impl<T> Window<T> {
}
/// Used for setting a toolbar on this window.
pub fn set_toolbar<TC: ToolbarController>(&self, toolbar: &Toolbar<TC>) {
pub fn set_toolbar<TC: ToolbarDelegate>(&self, toolbar: &Toolbar<TC>) {
unsafe {
let _: () = msg_send![&*self.objc, setToolbar:&*toolbar.objc_controller.0];
let _: () = msg_send![&*self.objc, setToolbar:&*toolbar.objc];
}
}
@ -396,89 +447,22 @@ impl<T> Window<T> {
}
}
impl<T> Window<T> where T: WindowDelegate + 'static {
/// Constructs a new Window with a `config` and `delegate`. Using a `WindowDelegate` enables
/// you to respond to window lifecycle events - visibility, movement, and so on. It also
/// enables easier structure of your codebase, and in a way simulates traditional class based
/// architectures... just without the subclassing.
pub fn with(config: WindowConfig, delegate: T) -> Self {
let delegate = Rc::new(RefCell::new(delegate));
let internal_callback_ptr = {
let cloned = Rc::clone(&delegate);
Rc::into_raw(cloned)
};
let objc = unsafe {
let alloc: id = msg_send![register_window_class_with_delegate::<T>(), alloc];
// Other types of backing (Retained/NonRetained) are archaic, dating back to the
// NeXTSTEP era, and are outright deprecated... so we don't allow setting them.
let buffered: NSUInteger = 2;
let dimensions: CGRect = config.initial_dimensions.into();
let window: id = msg_send![alloc, initWithContentRect:dimensions
styleMask:config.style
backing:buffered
defer:match config.defer {
true => YES,
false => NO
}
];
(&mut *window).set_ivar(WINDOW_DELEGATE_PTR, internal_callback_ptr as usize);
let _: () = msg_send![window, autorelease];
// This is very important! NSWindow is an old class and has some behavior that we need
// to disable, like... this. If we don't set this, we'll segfault entirely because the
// Objective-C runtime gets out of sync by releasing the window out from underneath of
// us.
let _: () = msg_send![window, setReleasedWhenClosed:NO];
// We set the window to be its own delegate - this is cleaned up inside `Drop`.
let _: () = msg_send![window, setDelegate:window];
ShareId::from_ptr(window)
};
{
let mut window_delegate = delegate.borrow_mut();
(*window_delegate).did_load(Window {
internal_callback_ptr: None,
delegate: None,
objc: objc.clone()
});
}
Window {
internal_callback_ptr: Some(internal_callback_ptr),
objc: objc,
delegate: Some(delegate)
}
}
}
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.
///
/// Note that only the originating `Window<T>` carries the internal callback ptr, and we
/// Note that only the originating `Window<T>` carries the delegate, and we
/// intentionally don't provide this when cloning it as a handler. This ensures that we only
/// release the backing Window when the original `Window<T>` is dropped.
///
/// Well, theoretically.
fn drop(&mut self) {
if let Some(ptr) = self.internal_callback_ptr {
if self.delegate.is_some() {
unsafe {
// Break the delegate - this shouldn't be an issue, but we should strive to be safe
// here anyway.
let _: () = msg_send![&*self.objc, setDelegate:nil];
// Bring this back and let it drop naturally.
let _ = Rc::from_raw(ptr);
}
}
}

View file

@ -2,7 +2,7 @@
//! module. There's a few different ones, and it's just... cleaner, if
//! it's organized here.
use crate::app::enums::PresentationOption;
use crate::app::PresentationOption;
use crate::window::Window;
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa
@ -13,7 +13,7 @@ pub trait WindowDelegate {
/// 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(&mut self, _window: Window) {}
fn did_load(&self, _window: Window) {}
/// Called when the user has attempted to close the window. NOT called when a user quits the
/// application. Return false here if you need to handle the edge case.