Further work on finishing up AppController interface
This commit is contained in:
parent
f1689d7cf9
commit
7b5ed88bb1
|
@ -2,8 +2,11 @@
|
|||
//! creates a custom `NSApplication` subclass that currently does nothing; this is meant as a hook
|
||||
//! for potential future use.
|
||||
|
||||
use std::unreachable;
|
||||
use std::ffi::c_void;
|
||||
use std::sync::Once;
|
||||
use std::unreachable;
|
||||
|
||||
use block::Block;
|
||||
|
||||
use cocoa::base::{id, nil, BOOL, YES, NO};
|
||||
use cocoa::foundation::{NSUInteger};
|
||||
|
@ -18,9 +21,11 @@ use crate::app::traits::AppController;
|
|||
use crate::constants::APP_PTR;
|
||||
use crate::error::AppKitError;
|
||||
use crate::printing::PrintSettings;
|
||||
use crate::utils::str_from;
|
||||
use crate::user_activity::UserActivity;
|
||||
use crate::utils::{map_nsarray, str_from};
|
||||
|
||||
/// A handy method for grabbing our `AppController` from the pointer.
|
||||
/// A handy method for grabbing our `AppController` from the pointer. This is different from our
|
||||
/// standard `utils` version as this doesn't require `RefCell` backing.
|
||||
fn app<T: AppController>(this: &Object) -> &T {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
||||
|
@ -113,14 +118,18 @@ extern fn should_handle_reopen<T: AppController>(this: &Object, _: Sel, _: id, h
|
|||
}
|
||||
|
||||
/// Fires when the application delegate receives a `applicationDockMenu:` request.
|
||||
extern fn dock_menu<T: AppController>(_this: &Object, _: Sel, _: id) -> id {
|
||||
nil
|
||||
extern fn dock_menu<T: AppController>(this: &Object, _: Sel, _: id) -> id {
|
||||
// @TODO: Confirm this is safe to do and not leaky.
|
||||
match app::<T>(this).dock_menu() {
|
||||
Some(mut menu) => &mut *menu.inner,
|
||||
None => nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when the application delegate receives a `application:willPresentError:` notification.
|
||||
extern fn will_present_error<T: AppController>(this: &Object, _: Sel, _: id, error: id) -> id {
|
||||
let error = AppKitError::new(error);
|
||||
app::<T>(this).will_present_error(*error).into_nserror()
|
||||
app::<T>(this).will_present_error(error).into_nserror()
|
||||
}
|
||||
|
||||
/// Fires when the application receives a `applicationDidChangeScreenParameters:` notification.
|
||||
|
@ -130,30 +139,44 @@ extern fn did_change_screen_parameters<T: AppController>(this: &Object, _: Sel,
|
|||
|
||||
/// Fires when the application receives a `application:willContinueUserActivityWithType:`
|
||||
/// notification.
|
||||
extern fn will_continue_user_activity_with_type<T: AppController>(_this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
|
||||
extern fn will_continue_user_activity_with_type<T: AppController>(this: &Object, _: Sel, _: id, activity_type: id) -> BOOL {
|
||||
let activity = str_from(activity_type);
|
||||
NO
|
||||
|
||||
/*match app::<T>(this).will_continue_user_activity_with_type(activity) {
|
||||
match app::<T>(this).will_continue_user_activity(activity) {
|
||||
true => YES,
|
||||
false => NO
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when the application receives a `application:continueUserActivity:restorationHandler:` notification.
|
||||
extern fn continue_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id, _: id) -> BOOL {
|
||||
NO
|
||||
extern fn continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id, handler: id) -> BOOL {
|
||||
// @TODO: This needs to support restorable objects, but it involves a larger question about how
|
||||
// much `NSObject` wrapping we want to do here. For now, pass the handler for whenever it's
|
||||
// useful.
|
||||
let activity = UserActivity::with_inner(activity);
|
||||
|
||||
match app::<T>(this).continue_user_activity(activity, || unsafe {
|
||||
let handler = handler as *const Block<(id,), c_void>;
|
||||
(*handler).call((nil,));
|
||||
}) {
|
||||
true => YES,
|
||||
false => NO
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when the application receives a
|
||||
/// `application:didFailToContinueUserActivityWithType:error:` message.
|
||||
extern fn failed_to_continue_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
||||
|
||||
extern fn failed_to_continue_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity_type: id, error: id) {
|
||||
app::<T>(this).failed_to_continue_user_activity(
|
||||
str_from(activity_type),
|
||||
AppKitError::new(error)
|
||||
);
|
||||
}
|
||||
|
||||
/// Fires when the application receives a `application:didUpdateUserActivity:` message.
|
||||
extern fn did_update_user_activity<T: AppController>(_this: &Object, _: Sel, _: id, _: id) {
|
||||
|
||||
extern fn did_update_user_activity<T: AppController>(this: &Object, _: Sel, _: id, activity: id) {
|
||||
let activity = UserActivity::with_inner(activity);
|
||||
app::<T>(this).updated_user_activity(activity);
|
||||
}
|
||||
|
||||
/// Fires when the application receives a `application:didRegisterForRemoteNotificationsWithDeviceToken:` message.
|
||||
|
@ -179,26 +202,13 @@ extern fn accepted_cloudkit_share<T: AppController>(_this: &Object, _: Sel, _: i
|
|||
|
||||
/// Fires when the application receives an `application:openURLs` message.
|
||||
extern fn open_urls<T: AppController>(this: &Object, _: Sel, _: id, file_urls: id) {
|
||||
app::<T>(this).open_urls(unsafe {
|
||||
let count: usize = msg_send![file_urls, count];
|
||||
let mut urls: Vec<Url> = Vec::with_capacity(count);
|
||||
let urls = map_nsarray(file_urls, |url| unsafe {
|
||||
let absolute_string = msg_send![url, absoluteString];
|
||||
let uri = str_from(absolute_string);
|
||||
Url::parse(uri)
|
||||
}).into_iter().filter_map(|url| url.ok()).collect();
|
||||
|
||||
let mut index = 0;
|
||||
loop {
|
||||
let url: id = msg_send![file_urls, objectAtIndex:index];
|
||||
let absolute_string: id = msg_send![url, absoluteString];
|
||||
let uri = str_from(absolute_string);
|
||||
|
||||
if let Ok(u) = Url::parse(uri) {
|
||||
urls.push(u);
|
||||
}
|
||||
|
||||
index += 1;
|
||||
if index == count { break; }
|
||||
}
|
||||
|
||||
urls
|
||||
});
|
||||
app::<T>(this).open_urls(urls);
|
||||
}
|
||||
|
||||
/// Fires when the application receives an `application:openFileWithoutUI:` message.
|
||||
|
@ -250,13 +260,24 @@ extern fn print_file<T: AppController>(this: &Object, _: Sel, _: id, file: id) -
|
|||
/// Fired when the application receives an `application:printFiles:withSettings:showPrintPanels:`
|
||||
/// message.
|
||||
extern fn print_files<T: AppController>(this: &Object, _: Sel, _: id, files: id, settings: id, show_print_panels: BOOL) -> NSUInteger {
|
||||
app::<T>(this).print_files(vec![], PrintSettings::default(), match show_print_panels {
|
||||
let files = map_nsarray(files, |file| {
|
||||
str_from(file).to_string()
|
||||
});
|
||||
|
||||
let settings = PrintSettings::with_inner(settings);
|
||||
|
||||
app::<T>(this).print_files(files, settings, match show_print_panels {
|
||||
YES => true,
|
||||
NO => false,
|
||||
_ => { unreachable!(); }
|
||||
}).into()
|
||||
}
|
||||
|
||||
/// Called when the application's occlusion state has changed.
|
||||
extern fn did_change_occlusion_state<T: AppController>(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.
|
||||
|
@ -314,6 +335,7 @@ pub(crate) fn register_app_controller_class<T: AppController>() -> *const Class
|
|||
|
||||
// Managing the Screen
|
||||
decl.add_method(sel!(applicationDidChangeScreenParameters:), did_change_screen_parameters::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationDidChangeOcclusionState:), did_change_occlusion_state::<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);
|
||||
|
|
|
@ -10,7 +10,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
|
||||
mod class;
|
||||
pub mod traits;
|
||||
pub mod types;
|
||||
pub mod enums;
|
||||
|
||||
use crate::constants::APP_PTR;
|
||||
use crate::menu::Menu;
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
use url::Url;
|
||||
|
||||
use crate::app::types::{TerminateResponse, PrintResponse};
|
||||
use crate::app::enums::{TerminateResponse, PrintResponse};
|
||||
use crate::error::AppKitError;
|
||||
use crate::menu::Menu;
|
||||
use crate::printing::types::PrintSettings;
|
||||
use crate::printing::settings::PrintSettings;
|
||||
use crate::user_activity::UserActivity;
|
||||
|
||||
pub struct CKShareMetaData;
|
||||
pub struct UserActivity;
|
||||
|
||||
/// Controllers interested in processing messages can implement this to respond to messages as
|
||||
/// they're dispatched. All messages come in on the main thread.
|
||||
|
@ -109,14 +109,16 @@ pub trait AppController {
|
|||
/// 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.
|
||||
fn continue_user_activity(&self, _activity: UserActivity) -> 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 did_fail_to_continue_user_activity(&self, _activity: UserActivity, _error: AppKitError) {}
|
||||
fn failed_to_continue_user_activity(&self, _activity_type: &str, _error: AppKitError) {}
|
||||
|
||||
/// Fired after the user activity object has been updated.
|
||||
fn did_update_user_activity(&self, _activity: UserActivity) {}
|
||||
fn updated_user_activity(&self, _activity: UserActivity) {}
|
||||
|
||||
/// Fires after the user accepted a CloudKit sharing invitation associated with your
|
||||
/// application.
|
||||
|
|
|
@ -26,7 +26,7 @@ impl AppKitError {
|
|||
/// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
|
||||
/// configure this. We pull out the information as it makes the error thread safe this way,
|
||||
/// which is... easier, in some cases.
|
||||
pub fn new(error: id) -> Box<Self> {
|
||||
pub fn new(error: id) -> Self {
|
||||
let (code, domain, description) = unsafe {
|
||||
let code: usize = msg_send![error, code];
|
||||
let domain: id = msg_send![error, domain];
|
||||
|
@ -35,11 +35,15 @@ impl AppKitError {
|
|||
(code, domain, description)
|
||||
};
|
||||
|
||||
Box::new(AppKitError {
|
||||
AppKitError {
|
||||
code: code,
|
||||
domain: str_from(domain).to_string(),
|
||||
description: str_from(description).to_string()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn boxed(error: id) -> Box<Self> {
|
||||
Box::new(AppKitError::new(error))
|
||||
}
|
||||
|
||||
/// Used for cases where we need to return an `NSError` back to the system (e.g, top-level
|
||||
|
|
|
@ -83,7 +83,7 @@ impl FileManager {
|
|||
let error: id = nil;
|
||||
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
||||
if result == NO {
|
||||
return Err(AppKitError::new(error));
|
||||
return Err(AppKitError::new(error).into());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ pub mod notifications;
|
|||
pub mod pasteboard;
|
||||
pub mod printing;
|
||||
pub mod toolbar;
|
||||
pub mod user_activity;
|
||||
pub mod utils;
|
||||
pub mod view;
|
||||
pub mod webview;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! Implements types used for printing (both configuring print jobs, as well as the act of printing
|
||||
//! itself).
|
||||
|
||||
pub mod types;
|
||||
pub use types::*;
|
||||
pub mod settings;
|
||||
pub use settings::PrintSettings;
|
||||
|
|
22
appkit/src/printing/settings.rs
Normal file
22
appkit/src/printing/settings.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
//! Represents settings for printing items. Backed by an `NSDictionary` in Objective-C, this struct
|
||||
//! aims to make it easier to query/process printing operations.
|
||||
|
||||
use cocoa::base::id;
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
/// `PrintSettings` represents options used in printing, typically passed to you by the
|
||||
/// application/user.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PrintSettings {
|
||||
pub inner: ShareId<Object>
|
||||
}
|
||||
|
||||
impl PrintSettings {
|
||||
/// Internal method, constructs a wrapper around the backing `NSDictionary` print settings.
|
||||
pub(crate) fn with_inner(inner: id) -> Self {
|
||||
PrintSettings {
|
||||
inner: unsafe { ShareId::from_ptr(inner) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
//! Represents settings for printing items.
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct PrintSettings;
|
20
appkit/src/user_activity.rs
Normal file
20
appkit/src/user_activity.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
//! A module wrapping `NSUserActivity`.
|
||||
|
||||
use cocoa::base::id;
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
/// Represents an `NSUserActivity`, which acts as a lightweight method to capture the state of your
|
||||
/// app.
|
||||
pub struct UserActivity {
|
||||
pub inner: ShareId<Object>
|
||||
}
|
||||
|
||||
impl UserActivity {
|
||||
/// An internal method for wrapping a system-provided activity.
|
||||
pub(crate) fn with_inner(object: id) -> Self {
|
||||
UserActivity {
|
||||
inner: unsafe { ShareId::from_ptr(object) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,28 @@ pub fn str_from(nsstring: id) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
/// A utility method for mapping over NSArray instances. There's a number of places where we want
|
||||
/// or need this functionality to provide Rust interfaces - this tries to do it in a way where the
|
||||
/// `Vec` doesn't need to resize after being allocated.
|
||||
pub fn map_nsarray<T, F>(array: id, transform: F) -> Vec<T>
|
||||
where F: Fn(id) -> T {
|
||||
let count: usize = unsafe { msg_send![array, count] };
|
||||
|
||||
let mut ret: Vec<T> = Vec::with_capacity(count);
|
||||
let mut index = 0;
|
||||
|
||||
loop {
|
||||
let file: id = unsafe { msg_send![array, objectAtIndex:index] };
|
||||
ret.push(transform(file));
|
||||
|
||||
index += 1;
|
||||
if index == count { break }
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
/// 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.
|
||||
|
|
Loading…
Reference in a new issue