Refactor rest of modules to use local foundation

This commit is contained in:
Ryan McGrath 2020-03-17 18:19:56 -07:00
parent c16dad564e
commit aacfc81b99
No known key found for this signature in database
GPG key ID: 811674B62B666830
21 changed files with 483 additions and 286 deletions

View file

@ -11,10 +11,10 @@ dispatch = "0.2.0"
libc = "0.2"
objc = "0.2.7"
objc_id = "0.1.1"
#uuid = { version = "0.8", features = ["v4"] }
uuid = { version = "0.8", features = ["v4"], optional = true }
url = "2.1.1"
[features]
cloudkit = []
user-notifications = []
user-notifications = ["uuid"]
enable-webview-downloading = []

View file

@ -1,6 +1,6 @@
//! Certain enums that are useful (response types, etc).
use cocoa::foundation::{NSInteger, NSUInteger};
use crate::foundation::{NSInteger, NSUInteger};
pub enum ModalResponse {
Ok,

View file

@ -4,16 +4,14 @@
use std::error::Error;
use std::sync::RwLock;
use cocoa::base::{id, nil, NO};
use cocoa::foundation::{NSString, NSUInteger};
use objc_id::Id;
use objc::runtime::{BOOL, Object};
use objc::{class, msg_send, sel, sel_impl};
use url::Url;
use crate::foundation::{id, nil, NO, NSString, NSUInteger};
use crate::error::AppKitError;
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
use crate::utils::str_from;
pub struct FileManager {
pub manager: RwLock<Id<Object>>
@ -58,8 +56,7 @@ impl FileManager {
create:NO
error:nil];
let s: id = msg_send![dir, absoluteString];
str_from(s)
NSString::wrap(msg_send![dir, absoluteString]).to_str()
};
Url::parse(directory).map_err(|e| e.into())
@ -69,12 +66,12 @@ impl FileManager {
/// an error on the Objective-C side, which we attempt to handle and bubble up as a result if
/// so.
pub fn move_item(&self, from: Url, to: Url) -> Result<(), Box<dyn Error>> {
unsafe {
let s = NSString::alloc(nil).init_str(from.as_str());
let from_url: id = msg_send![class!(NSURL), URLWithString:s];
let from = NSString::new(from.as_str());
let to = NSString::new(to.as_str());
let s2 = NSString::alloc(nil).init_str(to.as_str());
let to_url: id = msg_send![class!(NSURL), URLWithString:s2];
unsafe {
let from_url: id = msg_send![class!(NSURL), URLWithString:from.into_inner()];
let to_url: id = msg_send![class!(NSURL), URLWithString:to.into_inner()];
// This should potentially be write(), but the backing class handles this logic
// already, so... going to leave it as read.

View file

@ -4,14 +4,11 @@
use block::ConcreteBlock;
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSInteger, NSString};
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::utils::str_from;
use crate::foundation::{id, nil, YES, NO, NSInteger, NSString};
#[derive(Debug)]
pub struct FileSavePanel {
@ -54,7 +51,7 @@ impl FileSavePanel {
pub fn set_suggested_filename(&mut self, suggested_filename: &str) {
unsafe {
let filename = NSString::alloc(nil).init_str(suggested_filename);
let filename = NSString::new(suggested_filename);
let _: () = msg_send![&*self.panel, setNameFieldStringValue:filename];
}
}
@ -100,11 +97,12 @@ impl FileSavePanel {
pub fn get_url(panel: &Object) -> Option<String> {
unsafe {
let url: id = msg_send![&*panel, URL];
if url == nil {
None
} else {
let path: id = msg_send![url, path];
Some(str_from(path).to_string())
Some(NSString::wrap(path).to_str().to_string())
}
}
}

View file

@ -4,15 +4,12 @@
use block::ConcreteBlock;
use cocoa::base::{id, YES, NO};
use cocoa::foundation::NSInteger;
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{id, YES, NO, NSInteger, NSString};
use crate::filesystem::enums::ModalResponse;
use crate::utils::str_from;
#[derive(Debug)]
pub struct FileSelectPanel {
@ -156,8 +153,8 @@ pub fn get_urls(panel: &Object) -> Vec<String> {
}
let url: id = msg_send![urls, objectAtIndex:count-1];
let path: id = msg_send![url, absoluteString];
paths.push(str_from(path).to_string());
let path = NSString::wrap(msg_send![url, absoluteString]).to_str().to_string();
paths.push(path);
count -= 1;
}
}

View file

@ -18,6 +18,11 @@ pub struct Rect {
}
impl Rect {
/// Returns a new `Rect` initialized with the values specified.
pub fn new(top: f64, left: f64, width: f64, height: f64) -> Self {
Rect { top: top, left: left, width: width, height: height }
}
/// Returns a zero'd out Rect, with f64 (32-bit is mostly dead on Cocoa, so... this is "okay").
pub fn zero() -> Rect {
Rect {

View file

@ -29,7 +29,7 @@ pub mod constants;
pub mod dragdrop;
pub mod error;
pub mod events;
//pub mod filesystem;
pub mod filesystem;
pub mod foundation;
pub mod geometry;
pub mod layout;
@ -44,8 +44,8 @@ pub mod printing;
pub mod toolbar;
pub mod user_activity;
pub mod utils;
/*pub mod view;
pub mod webview;
pub mod view;
//pub mod webview;
pub mod window;
// We re-export these so that they can be used without increasing build times.
@ -69,13 +69,9 @@ pub mod prelude {
Window, WindowController, WindowHandle
};
pub use crate::webview::{
WebView, WebViewConfig, WebViewController
};
//pub use crate::webview::{
// WebView, WebViewConfig, WebViewController
//};
pub use crate::view::{View, ViewHandle, ViewController};
pub use appkit_derive::{
WindowWrapper, ViewWrapper
};
}*/
}

View file

@ -1,62 +0,0 @@
//! Wraps UNUserNotificationCenter for macOS. Note that this uses the newer
//! `UserNotifications.framework` API, which requires that your application be properly signed.
use block::ConcreteBlock;
use cocoa::base::{id, nil};
use cocoa::foundation::NSString;
use objc::{class, msg_send, sel, sel_impl};
use crate::notifications::Notification;
use crate::utils::str_from;
#[allow(non_upper_case_globals, non_snake_case)]
pub mod NotificationAuthOption {
pub const Badge: i32 = 1 << 0;
pub const Sound: i32 = 1 << 1;
pub const Alert: i32 = 1 << 2;
}
/// Acts as a central interface to the Notification Center on macOS.
pub struct NotificationCenter;
impl NotificationCenter {
/// Requests authorization from the user to send them notifications.
pub fn request_authorization(options: i32) {
unsafe {
let block = ConcreteBlock::new(|_: id, error: id| {
let msg: id = msg_send![error, localizedDescription];
let localized_description = str_from(msg);
if localized_description != "" {
println!("{:?}", localized_description);
}
});
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, requestAuthorizationWithOptions:options completionHandler:block.copy()];
}
}
/// Queues up a `Notification` to be displayed to the user.
pub fn notify(notification: Notification) {
let uuidentifier = format!("{}", uuid::Uuid::new_v4());
unsafe {
let identifier = NSString::alloc(nil).init_str(&uuidentifier);
let request: id = msg_send![class!(UNNotificationRequest), requestWithIdentifier:identifier content:&*notification.inner trigger:nil];
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, addNotificationRequest:request];
}
}
/// Removes all notifications that have been delivered (e.g, in the notification center).
pub fn remove_all_delivered_notifications() {
unsafe {
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, removeAllDeliveredNotifications];
}
}
}

View file

@ -0,0 +1,29 @@
//! Enums used in notifications - e.g, for customizing registration or appearance.
use crate::foundation::NSUInteger;
pub enum NotificationAuthOption {
Badge,
Sound,
Alert
}
impl From<NotificationAuthOption> for NSUInteger {
fn from(option: NotificationAuthOption) -> Self {
match option {
NotificationAuthOption::Badge => 1 << 0,
NotificationAuthOption::Sound => 1 << 1,
NotificationAuthOption::Alert => 1 << 2
}
}
}
impl From<&NotificationAuthOption> for NSUInteger {
fn from(option: &NotificationAuthOption) -> Self {
match option {
NotificationAuthOption::Badge => 1 << 0,
NotificationAuthOption::Sound => 1 << 1,
NotificationAuthOption::Alert => 1 << 2
}
}
}

View file

@ -1,7 +1,65 @@
//! Hoisting.
//! Wraps UNUserNotificationCenter for macOS. Note that this uses the newer
//! `UserNotifications.framework` API, which requires that your application be properly signed.
//!
//! To use this module, you must specify the `user-notifications` feature flag in your
//! `Cargo.toml`.
pub mod center;
pub use center::*;
use block::ConcreteBlock;
use objc::{class, msg_send, sel, sel_impl};
use uuid::Uuid;
use crate::foundation::{id, nil, NSString, NSUInteger};
pub mod enums;
pub use enums::NotificationAuthOption;
pub mod notifications;
pub use notifications::*;
pub use notifications::Notification;
/// Acts as a central interface to the Notification Center on macOS.
pub struct NotificationCenter;
impl NotificationCenter {
/// Requests authorization from the user to send them notifications.
pub fn request_authorization(options: &[NotificationAuthOption]) {
unsafe {
// @TODO: Revisit.
let block = ConcreteBlock::new(|_: id, error: id| {
let localized_description = NSString::new(msg_send![error, localizedDescription]).to_str();
if localized_description != "" {
println!("{:?}", localized_description);
}
});
let mut opts: NSUInteger = 0;
for opt in options {
let o: NSUInteger = opt.into();
opts = opts << o;
}
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, requestAuthorizationWithOptions:opts completionHandler:block.copy()];
}
}
/// Queues up a `Notification` to be displayed to the user.
pub fn notify(notification: Notification) {
let uuidentifier = format!("{}", Uuid::new_v4());
unsafe {
let identifier = NSString::new(&uuidentifier);
let request: id = msg_send![class!(UNNotificationRequest), requestWithIdentifier:identifier content:&*notification.0 trigger:nil];
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, addNotificationRequest:request];
}
}
/// Removes all notifications that have been delivered (e.g, in the notification center).
pub fn remove_all_delivered_notifications() {
unsafe {
let center: id = msg_send![class!(UNUserNotificationCenter), currentNotificationCenter];
let _: () = msg_send![center, removeAllDeliveredNotifications];
}
}
}

View file

@ -1,36 +1,28 @@
//! Acts as a (currently dumb) wrapper for `UNMutableNotificationContent`, which is what you mostly
//! need to pass to the notification center for things to work.
use cocoa::base::{id, nil};
use cocoa::foundation::NSString;
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NSString};
/// A wrapper for `UNMutableNotificationContent`. Retains the pointer from the Objective C side,
/// and is ultimately dropped upon sending.
pub struct Notification {
pub inner: Id<Object>
}
pub struct Notification(pub Id<Object>);
impl Notification {
/// Constructs a new `Notification`. This allocates `NSString`'s, as it has to do so for the
/// Objective C runtime - be aware if you're slaming this (you shouldn't be slamming this).
pub fn new(title: &str, body: &str) -> Self {
Notification {
inner: unsafe {
let cls = class!(UNMutableNotificationContent);
let content: id = msg_send![cls, new];
let title = NSString::new(title);
let body = NSString::new(body);
let title = NSString::alloc(nil).init_str(title);
let _: () = msg_send![content, setTitle:title];
let body = NSString::alloc(nil).init_str(body);
let _: () = msg_send![content, setBody:body];
Id::from_ptr(content)
}
}
Notification(unsafe {
let content: id = msg_send![class!(UNMutableNotificationContent), new];
let _: () = msg_send![content, setTitle:title];
let _: () = msg_send![content, setBody:body];
Id::from_ptr(content)
})
}
}

View file

@ -10,20 +10,17 @@
use std::rc::Rc;
use std::sync::Once;
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSUInteger};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{msg_send, sel, sel_impl};
use objc_id::Id;
use crate::foundation::{id, nil, YES, NO, NSUInteger};
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
use crate::dragdrop::DragInfo;
use crate::view::traits::ViewController;
use crate::utils::load;
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
return YES;

View file

@ -3,13 +3,11 @@
use std::sync::Once;
use cocoa::base::{id, NO};
use cocoa::foundation::{NSRect};
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NO, CGRect};
use crate::constants::VIEW_CONTROLLER_PTR;
use crate::geometry::Rect;
use crate::view::ViewController;
@ -18,7 +16,7 @@ use crate::view::class::register_view_class;
/// Loads and configures ye old NSView for this controller.
extern fn load_view<T: ViewController>(this: &mut Object, _: Sel) {
unsafe {
let zero: NSRect = Rect::zero().into();
let zero: CGRect = Rect::zero().into();
let view: id = msg_send![register_view_class::<T>(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let _: () = msg_send![view, setFrame:zero];

View file

@ -1,21 +1,19 @@
//! A wrapper for `NSViewController`. Uses interior mutability to
use std::cell::RefCell;
use std::rc::Rc;
use cocoa::base::{id, nil, YES};
use cocoa::foundation::NSArray;
//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your
//! `ViewController::did_load()` method. This method is safe to store and use, however as it's
//! UI-specific it's not thread safe.
//!
//! You can use this struct to configure how a view should look and layout. It implements
//! AutoLayout - for more information, see the AutoLayout tutorial.
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NSArray, NSString};
use crate::color::Color;
use crate::constants::{BACKGROUND_COLOR, VIEW_CONTROLLER_PTR};
use crate::constants::BACKGROUND_COLOR;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::view::controller::register_controller_class;
use crate::view::traits::ViewController;
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
@ -84,12 +82,15 @@ impl ViewHandle {
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
if let Some(objc) = &self.objc {
unsafe {
let types = NSArray::arrayWithObjects(nil, &types.iter().map(|t| {
t.to_nsstring()
}).collect::<Vec<id>>());
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = t.clone().into();
x.into_inner()
}).collect::<Vec<id>>().into();
let view: id = msg_send![*objc, view];
let _: () = msg_send![view, registerForDraggedTypes:types];
let _: () = msg_send![view, registerForDraggedTypes:types.into_inner()];
}
}
}
@ -108,107 +109,3 @@ impl ViewHandle {
}
}
}
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
/// of Cocoa is essentially Heap'd, so... well, enjoy.
#[derive(Clone)]
pub struct View<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: ViewHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> View<T> where T: ViewController + 'static {
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied view controller.
pub fn new(controller: T) -> Self {
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let inner = unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), new];
(&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
let view: id = msg_send![view_controller, view];
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
ShareId::from_ptr(view_controller)
};
let handle = ViewHandle::new(inner);
{
let mut vc = controller.borrow_mut();
(*vc).did_load(handle.clone());
}
View {
internal_callback_ptr: internal_callback_ptr,
objc_controller: handle,
controller: controller
}
}
pub fn set_background_color(&self, color: Color) {
self.objc_controller.set_background_color(color);
}
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
self.objc_controller.register_for_dragged_types(types);
}
pub fn top(&self) -> &LayoutAnchorY {
&self.objc_controller.top
}
pub fn leading(&self) -> &LayoutAnchorX {
&self.objc_controller.leading
}
pub fn trailing(&self) -> &LayoutAnchorX {
&self.objc_controller.trailing
}
pub fn bottom(&self) -> &LayoutAnchorY {
&self.objc_controller.bottom
}
pub fn width(&self) -> &LayoutAnchorDimension {
&self.objc_controller.width
}
pub fn height(&self) -> &LayoutAnchorDimension {
&self.objc_controller.height
}
}
impl<T> Layout for View<T> {
/// Returns the Objective-C object used for handling the view heirarchy.
fn get_backing_node(&self) -> Option<ShareId<Object>> {
self.objc_controller.objc.clone()
}
fn add_subview<V: Layout>(&self, subview: &V) {
self.objc_controller.add_subview(subview);
}
}
impl<T> std::fmt::Debug for View<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "View ({:p})", self)
}
}
impl<T> Drop for View<T> {
/// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) {
unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -1,9 +1,156 @@
//! This module implements tooling for constructing Views. Notably, it provides the following:
//!
//! - A `View` type, which holds your `impl ViewController` and handles routing around platform
//! lifecycle methods accordingly. This is a heap allocation.
//! - A `ViewController` trait, which enables you to hook into the `NSViewController` lifecycle
//! methods.
//! - A `ViewHandle` struct, which wraps a platform-provided `NSView` and enables you to configure
//! things such as appearance and layout.
//!
//! You can use it like the following:
//!
//! ```
//! use appkit::prelude::{View, ViewController, ViewHandle};
//! use appkit::color::rgb;
//!
//! #[derive(Default)]
//! pub struct MyView {
//! pub view: ViewHandle
//! }
//!
//! impl ViewController for MyView {
//! fn did_load(&mut self, view: ViewHandle) {
//! self.view = view;
//! self.view.set_background_color(rgb(0, 0, 0));
//! }
//! }
//! ```
//!
//! For more information and examples, consult the sample code distributed in the git repository.
pub(crate) mod class;
pub(crate) mod controller;
use std::rc::Rc;
use std::cell::RefCell;
use objc_id::ShareId;
use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl};
use crate::foundation::id;
use crate::color::Color;
use crate::constants::VIEW_CONTROLLER_PTR;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
mod class;
mod controller;
use controller::register_controller_class;
pub mod traits;
pub use traits::*;
pub use traits::ViewController;
pub mod view;
pub use view::{View, ViewHandle};
pub mod handle;
pub use handle::ViewHandle;
/// A `View` wraps two different controllers - one on the Objective-C/Cocoa side, which forwards
/// calls into your supplied `ViewController` trait object. This involves heap allocation, but all
/// of Cocoa is essentially Heap'd, so... well, enjoy.
#[derive(Clone)]
pub struct View<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: ViewHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> View<T> where T: ViewController + 'static {
/// Allocates and configures a `ViewController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied view controller.
pub fn new(controller: T) -> Self {
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let inner = unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), new];
(&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
let view: id = msg_send![view_controller, view];
(&mut *view).set_ivar(VIEW_CONTROLLER_PTR, internal_callback_ptr as usize);
ShareId::from_ptr(view_controller)
};
let handle = ViewHandle::new(inner);
{
let mut vc = controller.borrow_mut();
(*vc).did_load(handle.clone());
}
View {
internal_callback_ptr: internal_callback_ptr,
objc_controller: handle,
controller: controller
}
}
pub fn set_background_color(&self, color: Color) {
self.objc_controller.set_background_color(color);
}
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
self.objc_controller.register_for_dragged_types(types);
}
pub fn top(&self) -> &LayoutAnchorY {
&self.objc_controller.top
}
pub fn leading(&self) -> &LayoutAnchorX {
&self.objc_controller.leading
}
pub fn trailing(&self) -> &LayoutAnchorX {
&self.objc_controller.trailing
}
pub fn bottom(&self) -> &LayoutAnchorY {
&self.objc_controller.bottom
}
pub fn width(&self) -> &LayoutAnchorDimension {
&self.objc_controller.width
}
pub fn height(&self) -> &LayoutAnchorDimension {
&self.objc_controller.height
}
}
impl<T> Layout for View<T> {
/// Returns the Objective-C object used for handling the view heirarchy.
fn get_backing_node(&self) -> Option<ShareId<Object>> {
self.objc_controller.objc.clone()
}
fn add_subview<V: Layout>(&self, subview: &V) {
self.objc_controller.add_subview(subview);
}
}
impl<T> std::fmt::Debug for View<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "View ({:p})", self)
}
}
impl<T> Drop for View<T> {
/// A bit of extra cleanup for delegate callback pointers.
fn drop(&mut self) {
unsafe {
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -2,16 +2,16 @@
//! Cocoa and associated widgets. This also handles looping back
//! lifecycle events, such as window resizing or close events.
use cocoa::base::{id, YES, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSUInteger};
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NO, NSUInteger, CGRect};
use crate::geometry::Rect;
#[allow(non_upper_case_globals, non_snake_case)]
pub mod WindowStyle {
use cocoa::foundation::NSUInteger;
use crate::foundation::NSUInteger;
pub const Borderless: NSUInteger = 0;
pub const Titled: NSUInteger = 1 << 0;
@ -32,7 +32,7 @@ pub struct WindowConfig(pub Id<Object>);
impl Default for WindowConfig {
fn default() -> Self {
WindowConfig(unsafe {
let dimensions = NSRect::new(NSPoint::new(0., 0.), NSSize::new(800., 600.));
let dimensions: CGRect = Rect::new(0., 0., 800., 600.).into();
let style = WindowStyle::Resizable | WindowStyle::Miniaturizable | WindowStyle::UnifiedTitleAndToolbar |
WindowStyle::Closable | WindowStyle::Titled;

View file

@ -4,12 +4,11 @@
use std::rc::Rc;
use std::sync::Once;
use cocoa::base::id;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, sel, sel_impl};
use crate::foundation::id;
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::utils::load;
use crate::window::WindowController;

View file

@ -0,0 +1,15 @@
//! Enums used in Window construction and handling.
pub enum WindowTitleVisibility {
Visible,
Hidden
}
impl From<WindowTitleVisibility> for usize {
fn from(visibility: WindowTitleVisibility) -> usize {
match visibility {
WindowTitleVisibility::Visible => 0,
WindowTitleVisibility::Hidden => 1
}
}
}

View file

@ -4,15 +4,12 @@
//! We use `NSWindowController` as it has lifecycle methods that are useful, in addition to the
//! standard `NSWindowDelegate` methods.
use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSSize, NSString};
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::{id, nil, YES, NO, CGSize, NSString};
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
#[derive(Debug, Default, Clone)]
@ -24,7 +21,7 @@ impl WindowHandle {
pub fn set_title(&self, title: &str) {
if let Some(controller) = &self.0 {
unsafe {
let title = NSString::alloc(nil).init_str(title);
let title = NSString::new(title);
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setTitle:title];
}
@ -72,7 +69,7 @@ impl WindowHandle {
if let Some(controller) = &self.0 {
unsafe {
let window: id = msg_send![*controller, window];
let autosave = NSString::alloc(nil).init_str(name);
let autosave = NSString::new(name);
let _: () = msg_send![window, setFrameAutosaveName:autosave];
}
}
@ -82,7 +79,7 @@ impl WindowHandle {
pub fn set_minimum_content_size<F: Into<f64>>(&self, width: F, height: F) {
if let Some(controller) = &self.0 {
unsafe {
let size = NSSize::new(width.into(), height.into());
let size = CGSize::new(width.into(), height.into());
let window: id = msg_send![*controller, window];
let _: () = msg_send![window, setMinSize:size];
}

View file

@ -1,9 +1,59 @@
//! Implements wrappers and traits for `NSWindowController` and associated types.
//! Implements Window controls for macOS, by wrapping the various necessary moving pieces
//! (`NSWindow`, `NSWindowController`, and `NSWindowDelegate`) into one trait that you can
//! implement.
//!
//! For example:
//!
//! ```
//! use appkit::prelude::{AppController, Window};
//! use window::MyWindow;
//!
//! pub struct MyApp(Window<MyWindow>);
//!
//! impl MyApp {
//! pub fn new() -> Self {
//! MyApp(Window::new(MyWindow {
//! // Your things here
//! }))
//! }
//! }
//!
//! impl AppController for MyApp {
//! fn did_load(&self) {
//! self.0.show();
//! }
//! }
//! ```
//!
//! This simulate class-based structures well enough - you just can't subclass. Now you can do the
//! following:
//!
//! ```
//! use appkit::prelude::{WindowController, WindowHandle};
//!
//! pub struct MyWindow;
//!
//! impl WindowController for MyWindow {
//! fn did_load(&mut self, handle: WindowHandle) {}
//! }
//! ```
pub mod traits;
pub use traits::WindowController;
use std::rc::Rc;
use std::cell::RefCell;
use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::foundation::{id, nil};
use crate::constants::WINDOW_CONTROLLER_PTR;
use crate::layout::traits::Layout;
use crate::toolbar::{Toolbar, ToolbarController};
mod controller;
use controller::register_window_controller_class;
pub mod enums;
pub use enums::{WindowTitleVisibility};
pub mod config;
pub use config::{WindowConfig, WindowStyle};
@ -11,5 +61,106 @@ pub use config::{WindowConfig, WindowStyle};
pub mod handle;
pub use handle::WindowHandle;
pub mod window;
pub use window::{Window, WindowTitleVisibility};
pub mod traits;
pub use traits::WindowController;
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Clone, Debug)]
pub struct Window<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: WindowHandle,
pub controller: Rc<RefCell<T>>
}
impl<T> Window<T> where T: WindowController + 'static {
/// Allocates and configures a `WindowController` in the Objective-C/Cocoa runtime that maps over
/// to your supplied controller.
///
/// Now, you may look at this and go "hey, the hell is going on here - why don't you make the
/// `NSWindow` in `[NSWindowController loadWindow]`?
///
/// This is a great question. It's because NSWindowController is... well, broken or buggy -
/// pick a term, either works. It's optimized for loading from xib/nib files, and attempting to
/// get loadWindow to fire properly is a pain in the rear (you're fighting a black box).
///
/// This is why we do this work here, but for things subclassing `NSViewController`, we go with
/// the route of implementing `loadView`.
///
/// APPKIT!
pub fn new(controller: T) -> Self {
let window = controller.config().0;
let controller = Rc::new(RefCell::new(controller));
let internal_callback_ptr = {
let cloned = Rc::clone(&controller);
Rc::into_raw(cloned)
};
let inner = unsafe {
let window_controller_class = register_window_controller_class::<T>();
let controller_alloc: id = msg_send![window_controller_class, alloc];
let controller: id = msg_send![controller_alloc, initWithWindow:window];
(&mut *controller).set_ivar(WINDOW_CONTROLLER_PTR, internal_callback_ptr as usize);
let window: id = msg_send![controller, window];
let _: () = msg_send![window, setDelegate:controller];
ShareId::from_ptr(controller)
};
{
let mut vc = controller.borrow_mut();
(*vc).did_load(WindowHandle(Some(inner.clone())));
}
Window {
internal_callback_ptr: internal_callback_ptr,
objc_controller: WindowHandle(Some(inner)),
controller: controller
}
}
/// Sets the title for this window.
pub fn set_title(&self, title: &str) {
self.objc_controller.set_title(title.into());
}
/// Sets the toolbar for this window.
pub fn set_toolbar<TC: ToolbarController + 'static>(&self, toolbar: &Toolbar<TC>) {
self.objc_controller.set_toolbar(toolbar);
}
/// Sets the content view controller for the window.
pub fn set_content_view_controller<VC: Layout + 'static>(&self, view_controller: &VC) {
self.objc_controller.set_content_view_controller(view_controller);
}
/// Shows the window, running a configuration pass if necessary.
pub fn show(&self) {
self.objc_controller.show();
}
/// Closes the window.
pub fn close(&self) {
self.objc_controller.close();
}
}
impl<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.
fn drop(&mut self) {
unsafe {
if let Some(objc_controller) = &self.objc_controller.0 {
let window: id = msg_send![*objc_controller, window];
let _: () = msg_send![window, setDelegate:nil];
}
let _ = Rc::from_raw(self.internal_callback_ptr);
}
}
}

View file

@ -16,23 +16,9 @@ use crate::window::handle::WindowHandle;
use crate::window::traits::WindowController;
use crate::window::controller::register_window_controller_class;
pub enum WindowTitleVisibility {
Visible,
Hidden
}
impl From<WindowTitleVisibility> for usize {
fn from(visibility: WindowTitleVisibility) -> usize {
match visibility {
WindowTitleVisibility::Visible => 0,
WindowTitleVisibility::Hidden => 1
}
}
}
/// A `Window` represents your way of interacting with an `NSWindow`. It wraps the various moving
/// pieces to enable you to focus on reacting to lifecycle methods and doing your thing.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct Window<T> {
internal_callback_ptr: *const RefCell<T>,
pub objc_controller: WindowHandle,