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:
parent
4266c4c8dc
commit
27e534a612
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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. :)
|
||||
|
|
|
@ -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:
|
||||
///
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
10
src/lib.rs
10
src/lib.rs
|
@ -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;
|
||||
|
|
52
src/notification_center/mod.rs
Normal file
52
src/notification_center/mod.rs
Normal 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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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};
|
|
@ -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| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).collect::<Vec<id>>().into()
|
||||
};
|
||||
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).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();
|
||||
let identifiers: NSArray = toolbar.default_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
(*t).default_item_identifiers().iter().map(|identifier| {
|
||||
NSString::new(identifier).into_inner()
|
||||
}).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();
|
||||
|
||||
|
|
|
@ -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
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
52
src/user_defaults.rs
Normal 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()];
|
||||
}
|
||||
}
|
||||
}
|
32
src/utils.rs
32
src/utils.rs
|
@ -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)]
|
||||
|
|
|
@ -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,373 +28,197 @@ 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 s = window.will_resize(size.width as f64, size.height as f64);
|
||||
|
||||
let result = {
|
||||
let window = window.borrow();
|
||||
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
|
||||
CGSize {
|
||||
width: s.0 as CGFloat,
|
||||
height: s.1 as CGFloat
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(
|
||||
size.width as f64,
|
||||
size.height as f64
|
||||
);
|
||||
let (width, height) = window.content_size_for_full_screen(
|
||||
size.width as f64,
|
||||
size.height as f64
|
||||
);
|
||||
|
||||
CGSize {
|
||||
width: width as CGFloat,
|
||||
height: height as CGFloat
|
||||
}
|
||||
};
|
||||
|
||||
Rc::into_raw(window);
|
||||
|
||||
result
|
||||
CGSize {
|
||||
width: width as CGFloat,
|
||||
height: height as CGFloat
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
} else {
|
||||
let mut opts: NSUInteger = 0;
|
||||
for opt in options.unwrap() {
|
||||
opts = opts << NSUInteger::from(opt);
|
||||
}
|
||||
|
||||
Some(opts)
|
||||
if desired_opts.is_none() {
|
||||
options
|
||||
} else {
|
||||
let mut opts: NSUInteger = 0;
|
||||
for opt in desired_opts.unwrap() {
|
||||
opts = opts << NSUInteger::from(opt);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Rc::into_raw(window);
|
||||
window.did_expose();
|
||||
}
|
||||
|
||||
|
||||
/// Injects an `NSWindow` subclass, with some callback and pointer ivars for what we
|
||||
/// need to do.
|
||||
pub(crate) fn register_window_class() -> *const Class {
|
||||
|
|
|
@ -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()
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue