From 0446227a8d6319ffda6af2dac41d076bcc4af914 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Wed, 4 Mar 2020 18:33:11 -0800 Subject: [PATCH] Ongoing changes for the core. - Implemented basic support for `NSPasteBoardType` and drag/drop events. Not complete, but close. - `geometry.rs` implements a basic `Rect`, with some conversion logic for `NSRect`. Nothing special. - Changes to `Window` to remove some of the forced delegate pattern. Autosave name is now just another setter. - `WebView` no longer has a backing `View` by default, so implementors need to handle that on their own. - Beginning to wrap `NSFileManager`, which is kind of important given that this supports both Sandboxed and non-Sandboxed apps. - Documentation work ongoing. --- appkit/src/collection_view/mod.rs | 0 appkit/src/dragdrop.rs | 47 +++++++ appkit/src/file_panel/enums.rs | 30 ----- appkit/src/filesystem/enums.rs | 115 ++++++++++++++++++ appkit/src/filesystem/manager.rs | 69 +++++++++++ appkit/src/{file_panel => filesystem}/mod.rs | 6 +- appkit/src/{file_panel => filesystem}/save.rs | 0 .../src/{file_panel => filesystem}/select.rs | 2 +- .../src/{file_panel => filesystem}/traits.rs | 8 +- appkit/src/geometry.rs | 39 ++++++ appkit/src/lib.rs | 30 ++--- appkit/src/pasteboard.rs | 79 ++++++++++++ appkit/src/view/class.rs | 63 +++++++++- appkit/src/view/controller.rs | 76 ++++-------- appkit/src/view/mod.rs | 9 ++ appkit/src/view/traits.rs | 20 +++ appkit/src/view/view.rs | 71 +++++++++++ appkit/src/webview/controller.rs | 22 +--- appkit/src/webview/webview.rs | 6 +- appkit/src/window/traits.rs | 8 +- appkit/src/window/window.rs | 49 ++++++-- 21 files changed, 605 insertions(+), 144 deletions(-) create mode 100644 appkit/src/collection_view/mod.rs create mode 100644 appkit/src/dragdrop.rs delete mode 100644 appkit/src/file_panel/enums.rs create mode 100644 appkit/src/filesystem/enums.rs create mode 100644 appkit/src/filesystem/manager.rs rename appkit/src/{file_panel => filesystem}/mod.rs (68%) rename appkit/src/{file_panel => filesystem}/save.rs (100%) rename appkit/src/{file_panel => filesystem}/select.rs (99%) rename appkit/src/{file_panel => filesystem}/traits.rs (79%) create mode 100644 appkit/src/geometry.rs create mode 100644 appkit/src/pasteboard.rs create mode 100644 appkit/src/view/traits.rs create mode 100644 appkit/src/view/view.rs diff --git a/appkit/src/collection_view/mod.rs b/appkit/src/collection_view/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/appkit/src/dragdrop.rs b/appkit/src/dragdrop.rs new file mode 100644 index 0000000..d2bc26b --- /dev/null +++ b/appkit/src/dragdrop.rs @@ -0,0 +1,47 @@ +//! This module contains various bits and pieces for drag and drop operations. They're shared +//! across the codebase, hence why they're here - they're not currently exhaustive, so feel free to +//! tinker and pull request. + +use cocoa::foundation::NSUInteger; + +/// Represents operations that can happen for a given drag/drop scenario. +pub enum DragOperation { + /// No drag operations are allowed. + None, + + /// The data represented by the image can be copied. + Copy, + + /// The data can be shared. + Link, + + /// The operation can be defined by the destination. + Generic, + + /// The operation is negotiated privately between the source and the destination. + Private, + + /// The data can be moved. + Move, + + /// The data can be deleted. + Delete, + + // All of the above. + // @TODO: NSUIntegerMax, a tricky beast + // Every +} + +impl From for NSUInteger { + fn from(op: DragOperation) -> Self { + match op { + DragOperation::None => 0, + DragOperation::Copy => 1, + DragOperation::Link => 2, + DragOperation::Generic => 4, + DragOperation::Private => 8, + DragOperation::Move => 16, + DragOperation::Delete => 32 + } + } +} diff --git a/appkit/src/file_panel/enums.rs b/appkit/src/file_panel/enums.rs deleted file mode 100644 index 0cf5c0e..0000000 --- a/appkit/src/file_panel/enums.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Certain enums that are useful (response types, etc). - -use cocoa::foundation::{NSInteger}; - -pub enum ModalResponse { - Ok, - Continue, - Canceled, - Stopped, - Aborted, - FirstButtonReturned, - SecondButtonReturned, - ThirdButtonReturned -} - -impl From for ModalResponse { - fn from(i: NSInteger) -> Self { - match i { - 1 => ModalResponse::Ok, - 0 => ModalResponse::Canceled, - 1000 => ModalResponse::FirstButtonReturned, - 1001 => ModalResponse::SecondButtonReturned, - 1002 => ModalResponse::ThirdButtonReturned, - -1000 => ModalResponse::Stopped, - -1001 => ModalResponse::Aborted, - -1002 => ModalResponse::Continue, - e => { panic!("Unknown NSModalResponse sent back! {}", e); } - } - } -} diff --git a/appkit/src/filesystem/enums.rs b/appkit/src/filesystem/enums.rs new file mode 100644 index 0000000..1802418 --- /dev/null +++ b/appkit/src/filesystem/enums.rs @@ -0,0 +1,115 @@ +//! Certain enums that are useful (response types, etc). + +use cocoa::foundation::{NSInteger, NSUInteger}; + +pub enum ModalResponse { + Ok, + Continue, + Canceled, + Stopped, + Aborted, + FirstButtonReturned, + SecondButtonReturned, + ThirdButtonReturned +} + +impl From for ModalResponse { + fn from(i: NSInteger) -> Self { + match i { + 1 => ModalResponse::Ok, + 0 => ModalResponse::Canceled, + 1000 => ModalResponse::FirstButtonReturned, + 1001 => ModalResponse::SecondButtonReturned, + 1002 => ModalResponse::ThirdButtonReturned, + -1000 => ModalResponse::Stopped, + -1001 => ModalResponse::Aborted, + -1002 => ModalResponse::Continue, + e => { panic!("Unknown NSModalResponse sent back! {}", e); } + } + } +} + +pub enum SearchPathDomainMask { + User, + Local, + Network, + Domain, + AllDomains +} + +impl From for NSUInteger { + fn from(mask: SearchPathDomainMask) -> Self { + match mask { + SearchPathDomainMask::User => 1, + SearchPathDomainMask::Local => 2, + SearchPathDomainMask::Network => 4, + SearchPathDomainMask::Domain => 8, + SearchPathDomainMask::AllDomains => 0x0ffff + } + } +} + +pub enum SearchPathDirectory { + Applications, + DemoApplications, + DeveloperApplications, + AdminApplications, + Library, + Developer, + User, + Documentation, + Documents, + CoreServices, + AutosavedInformation, + Desktop, + Caches, + ApplicationSupport, + Downloads, + InputMethods, + Movies, + Music, + Pictures, + PrinterDescription, + SharedPublic, + PreferencePanes, + ApplicationScripts, + ItemReplacement, + AllApplications, + AllLibraries, + Trash +} + +impl From for NSUInteger { + fn from(directory: SearchPathDirectory) -> Self { + match directory { + SearchPathDirectory::Applications => 1, + SearchPathDirectory::DemoApplications => 2, + SearchPathDirectory::DeveloperApplications => 3, + SearchPathDirectory::AdminApplications => 4, + SearchPathDirectory::Library => 5, + SearchPathDirectory::Developer => 6, + SearchPathDirectory::User => 7, + SearchPathDirectory::Documentation => 8, + SearchPathDirectory::Documents => 9, + SearchPathDirectory::CoreServices => 10, + SearchPathDirectory::AutosavedInformation => 11, + SearchPathDirectory::Desktop => 12, + SearchPathDirectory::Caches => 13, + SearchPathDirectory::ApplicationSupport => 14, + SearchPathDirectory::Downloads => 15, + SearchPathDirectory::InputMethods => 16, + SearchPathDirectory::Movies => 17, + SearchPathDirectory::Music => 18, + SearchPathDirectory::Pictures => 19, + SearchPathDirectory::PrinterDescription => 20, + SearchPathDirectory::SharedPublic => 21, + SearchPathDirectory::PreferencePanes => 22, + SearchPathDirectory::ApplicationScripts => 23, + + SearchPathDirectory::ItemReplacement => 99, + SearchPathDirectory::AllApplications => 100, + SearchPathDirectory::AllLibraries => 101, + SearchPathDirectory::Trash => 102 + } + } +} diff --git a/appkit/src/filesystem/manager.rs b/appkit/src/filesystem/manager.rs new file mode 100644 index 0000000..a5adc2a --- /dev/null +++ b/appkit/src/filesystem/manager.rs @@ -0,0 +1,69 @@ +//! A wrapper for `NSFileManager`, which is necessary for macOS/iOS (the sandbox makes things +//! tricky, and this transparently handles it for you). + +use std::rc::Rc; +use std::cell::RefCell; + +use cocoa::base::{id, nil, NO}; +use cocoa::foundation::{NSInteger, NSUInteger}; + +use objc_id::Id; +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; + +use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask}; +use crate::utils::str_from; + +pub struct FileManagerInner { + pub manager: Id +} + +impl Default for FileManagerInner { + fn default() -> Self { + FileManagerInner { + manager: unsafe { + let manager: id = msg_send![class!(NSFileManager), defaultManager]; + Id::from_ptr(manager) + } + } + } +} + +impl FileManagerInner { + pub fn get_path(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result> { + let dir: NSUInteger = directory.into(); + let mask: NSUInteger = in_domain.into(); + + unsafe { + let dir: id = msg_send![&*self.manager, URLForDirectory:dir + inDomain:mask + appropriateForURL:nil + create:NO + error:nil]; + + let s: id = msg_send![dir, path]; + Ok(str_from(s).to_string()) + } + } +} + +#[derive(Default)] +pub struct FileManager(Rc>); + +impl FileManager { + pub fn new() -> Self { + FileManager(Rc::new(RefCell::new(FileManagerInner { + manager: unsafe { + let manager: id = msg_send![class!(NSFileManager), new]; + Id::from_ptr(manager) + } + }))) + } + + pub fn get_path(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result> { + let manager = self.0.borrow(); + manager.get_path(directory, in_domain) + } + + //pub fn contents_of(directory: &str, properties: &[ +} diff --git a/appkit/src/file_panel/mod.rs b/appkit/src/filesystem/mod.rs similarity index 68% rename from appkit/src/file_panel/mod.rs rename to appkit/src/filesystem/mod.rs index e745a7c..ff54dea 100644 --- a/appkit/src/file_panel/mod.rs +++ b/appkit/src/filesystem/mod.rs @@ -1,10 +1,12 @@ - pub mod enums; pub use enums::*; +pub mod manager; +pub use manager::FileManager; + pub mod traits; -pub use traits::OpenSaveController; +pub use traits::*; pub mod save; pub use save::FileSavePanel; diff --git a/appkit/src/file_panel/save.rs b/appkit/src/filesystem/save.rs similarity index 100% rename from appkit/src/file_panel/save.rs rename to appkit/src/filesystem/save.rs diff --git a/appkit/src/file_panel/select.rs b/appkit/src/filesystem/select.rs similarity index 99% rename from appkit/src/file_panel/select.rs rename to appkit/src/filesystem/select.rs index 2397b82..1c24cbb 100644 --- a/appkit/src/file_panel/select.rs +++ b/appkit/src/filesystem/select.rs @@ -11,7 +11,7 @@ use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::ShareId; -use crate::file_panel::enums::ModalResponse; +use crate::filesystem::enums::ModalResponse; use crate::utils::str_from; #[derive(Debug)] diff --git a/appkit/src/file_panel/traits.rs b/appkit/src/filesystem/traits.rs similarity index 79% rename from appkit/src/file_panel/traits.rs rename to appkit/src/filesystem/traits.rs index d8e4e00..c7d968f 100644 --- a/appkit/src/file_panel/traits.rs +++ b/appkit/src/filesystem/traits.rs @@ -1,6 +1,5 @@ //! A trait that you can implement to handle open and save file dialogs. This more or less maps //! over to `NSOpenPanel` and `NSSavePanel` handling. - pub trait OpenSaveController { /// Called when the user has entered a filename (typically, during saving). `confirmed` /// indicates whether or not they hit the save button. @@ -19,3 +18,10 @@ pub trait OpenSaveController { /// Determine whether the specified URL should be enabled in the Open panel. fn should_enable_url(&self, _url: &str) -> bool { true } } + +/// A trait you can implement for working with the underlying filesystem. This is important, +/// notably, because sandboxed applications have different working restrictions surrounding what +/// they can access. +pub trait FileManagerController { + +} diff --git a/appkit/src/geometry.rs b/appkit/src/geometry.rs new file mode 100644 index 0000000..a42b4fd --- /dev/null +++ b/appkit/src/geometry.rs @@ -0,0 +1,39 @@ +//! Wrapper methods for various geometry types (rects, sizes, ec). + +use cocoa::foundation::{NSRect, NSPoint, NSSize}; + +/// A struct that represents a box - top, left, width and height. +pub struct Rect { + /// Distance from the top, in points. + pub top: f64, + + /// Distance from the left, in points. + pub left: f64, + + /// Width, in points. + pub width: f64, + + /// Height, in points. + pub height: f64 +} + +impl Rect { + /// Returns a zero'd out Rect, with f64 (32-bit is mostly dead on Cocoa, so... this is "okay"). + pub fn zero() -> Rect { + Rect { + top: 0.0, + left: 0.0, + width: 0.0, + height: 0.0 + } + } +} + +impl From for NSRect { + fn from(rect: Rect) -> NSRect { + NSRect::new( + NSPoint::new(rect.top, rect.left), + NSSize::new(rect.width, rect.height) + ) + } +} diff --git a/appkit/src/lib.rs b/appkit/src/lib.rs index ee41206..94e62f5 100644 --- a/appkit/src/lib.rs +++ b/appkit/src/lib.rs @@ -20,27 +20,23 @@ pub use objc_id::ShareId; pub use objc::runtime::Object; pub use cocoa::base::id; -pub trait ViewWrapper { - fn get_handle(&self) -> Option>; -} - -pub trait ViewController { - fn did_load(&self); -} - pub mod alert; pub mod app; -pub mod events; -pub mod menu; pub mod button; -pub mod file_panel; -pub mod toolbar; -pub mod notifications; -pub mod webview; -pub mod view; -pub mod window; +pub mod collection_view; +pub mod dragdrop; +pub mod events; +pub mod filesystem; +pub mod geometry; +pub mod menu; pub mod networking; +pub mod notifications; +pub mod pasteboard; +pub mod toolbar; pub mod utils; +pub mod view; +pub mod webview; +pub mod window; pub mod prelude { pub use crate::app::{App, AppDelegate}; @@ -59,7 +55,7 @@ pub mod prelude { WebView, WebViewConfig, WebViewController }; - pub use crate::{ViewController, ViewWrapper}; + pub use crate::view::{View, ViewController, ViewWrapper}; pub use appkit_derive::{ WindowWrapper diff --git a/appkit/src/pasteboard.rs b/appkit/src/pasteboard.rs new file mode 100644 index 0000000..33baf70 --- /dev/null +++ b/appkit/src/pasteboard.rs @@ -0,0 +1,79 @@ +//! This module provides some basic wrappers for PasteBoard functionality. It's currently not an +//! exhaustive clone, but feel free to pull request accordingly! + +use cocoa::base::{id, nil}; +use cocoa::foundation::NSString; + +/// Represents different PasteBoard types that can be referred to. +#[derive(Debug, Copy, Clone)] +pub enum PasteBoardType { + /// URL data for one file or resource. + URL, + + /// Color data. + Color, + + /// A file URL. + FileURL, + + /// Font and character information. + Font, + + /// Type for HTML content. + HTML, + + /// Multiple text selection. + MultipleTextSelection, + + /// PDF data. + PDF, + + /// PNG image data. + PNG, + + /// Rich Text Format (RTF) data. + RTF, + + /// RTFD formatted file contents. + RTFD, + + /// Paragraph formatting information. + Ruler, + + /// Sound data. + Sound, + + /// String data. + String, + + /// Tab-separated fields of text. + TabularText, + + /// Tag Image File Format (TIFF) data. + TIFF +} + +impl PasteBoardType { + /// Creates an `NSString` out of the underlying type. + pub fn to_nsstring(&self) -> id { + unsafe { + NSString::alloc(nil).init_str(match self { + PasteBoardType::URL => "public.url", + PasteBoardType::Color => "com.apple.cocoa.pasteboard.color", + PasteBoardType::FileURL => "public.file-url", + PasteBoardType::Font => "com.apple.cocoa.pasteboard.character-formatting", + PasteBoardType::HTML => "public.html", + PasteBoardType::MultipleTextSelection => "com.apple.cocoa.pasteboard.multiple-text-selection", + PasteBoardType::PDF => "com.adobe.pdf", + PasteBoardType::PNG => "public.png", + PasteBoardType::RTF => "public.rtf", + PasteBoardType::RTFD => "com.apple.flat-rtfd", + PasteBoardType::Ruler => "com.apple.cocoa.pasteboard.paragraph-formatting", + PasteBoardType::Sound => "com.apple.cocoa.pasteboard.sound", + PasteBoardType::String => "public.utf8-plain-text", + PasteBoardType::TabularText => "public.utf8-tab-separated-values-text", + PasteBoardType::TIFF => "public.tiff", + }) + } + } +} diff --git a/appkit/src/view/class.rs b/appkit/src/view/class.rs index ee10ebd..753a26d 100644 --- a/appkit/src/view/class.rs +++ b/appkit/src/view/class.rs @@ -9,12 +9,16 @@ use std::sync::Once; -use cocoa::base::{id, nil, YES}; +use cocoa::base::{id, nil, YES, NO}; +use cocoa::foundation::{NSUInteger}; use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel, BOOL}; use objc::{class, msg_send, sel, sel_impl}; +use crate::view::VIEW_CONTROLLER_PTR; +use crate::view::traits::ViewController; + /// 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; @@ -31,9 +35,53 @@ extern fn update_layer(this: &Object, _: Sel) { } } +/// Called when a drag/drop operation has entered this view. +extern fn dragging_entered(this: &mut Object, _: Sel, _: id) -> NSUInteger { + unsafe { + let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR); + let view = ptr as *const T; + (*view).dragging_entered().into() + } +} + +/// Called when a drag/drop operation has entered this view. +extern fn prepare_for_drag_operation(this: &mut Object, _: Sel, _: id) -> BOOL { + unsafe { + let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR); + let view = ptr as *const T; + + match (*view).prepare_for_drag_operation() { + true => YES, + false => NO + } + } +} + +/// Called when a drag/drop operation has entered this view. +extern fn perform_drag_operation(this: &mut Object, _: Sel, _: id) -> BOOL { + unsafe { + let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR); + let view = ptr as *const T; + + match (*view).perform_drag_operation() { + true => YES, + false => NO + } + } +} + +/// Called when a drag/drop operation has entered this view. +extern fn dragging_exited(this: &mut Object, _: Sel, _: id) { + unsafe { + let ptr: usize = *this.get_ivar(VIEW_CONTROLLER_PTR); + let view = ptr as *const T; + (*view).dragging_exited(); + } +} + /// Injects an `NSView` subclass, with some callback and pointer ivars for what we /// need to do. -pub(crate) fn register_view_class() -> *const Class { +pub(crate) fn register_view_class() -> *const Class { static mut VIEW_CLASS: *const Class = 0 as *const Class; static INIT: Once = Once::new(); @@ -41,10 +89,19 @@ pub(crate) fn register_view_class() -> *const Class { let superclass = Class::get("NSView").unwrap(); let mut decl = ClassDecl::new("RSTView", superclass).unwrap(); + // A pointer to the "view controller" on the Rust side. It's expected that this doesn't + // move. + decl.add_ivar::(VIEW_CONTROLLER_PTR); + decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL); - decl.add_method(sel!(requiresConstraintBasedLayout), enforce_normalcy as extern fn(&Object, _) -> BOOL); decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL); decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _)); + + // Drag and drop operations (e.g, accepting files) + decl.add_method(sel!(draggingEntered:), dragging_entered:: as extern fn (&mut Object, _, _) -> NSUInteger); + decl.add_method(sel!(prepareForDragOperation:), prepare_for_drag_operation:: as extern fn (&mut Object, _, _) -> BOOL); + decl.add_method(sel!(performDragOperation:), perform_drag_operation:: as extern fn (&mut Object, _, _) -> BOOL); + decl.add_method(sel!(draggingExited:), dragging_exited:: as extern fn (&mut Object, _, _)); VIEW_CLASS = decl.register(); }); diff --git a/appkit/src/view/controller.rs b/appkit/src/view/controller.rs index cace8cb..57d48c3 100644 --- a/appkit/src/view/controller.rs +++ b/appkit/src/view/controller.rs @@ -1,69 +1,43 @@ -//! Hoists a basic NSView. In our current particular use case, -//! this is primarily used as the ContentView for a window. From there, -//! we configure an NSToolbar and WKWebview on top of them. +//! Hoists a basic `NSViewController`. We use `NSViewController` rather than plain `NSView` as +//! we're interested in the lifecycle methods and events. use std::sync::Once; -use cocoa::base::{id, YES}; -use cocoa::foundation::{NSRect, NSPoint, NSSize}; +use cocoa::base::{id, nil, YES, NO}; +use cocoa::foundation::{NSRect, NSUInteger}; -use objc_id::Id; use objc::declare::ClassDecl; -use objc::runtime::{Class, Object, Sel, BOOL}; -use objc::{msg_send, sel, sel_impl}; +use objc::runtime::{Class, Object, Sel}; +use objc::{class, msg_send, sel, sel_impl}; -/// A trait for handling the view lifecycle. -pub trait View { - fn did_load(&mut self) {} -} +use crate::geometry::Rect; +use crate::view::{VIEW_CONTROLLER_PTR, ViewController}; +use crate::view::class::register_view_class; -/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime -/// where our `NSWindow` and associated delegate live. -pub struct View { - pub inner: Id -} - -impl View { - /// 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. - pub fn new() -> Self { - let inner = unsafe { - let rect_zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)); - let alloc: id = msg_send![register_class(), alloc]; - let view: id = msg_send![alloc, initWithFrame:rect_zero]; - let _: () = msg_send![view, setWantsLayer:YES]; - let _: () = msg_send![view, setLayerContentsRedrawPolicy:1]; - Id::from_ptr(view) - }; - - View { - inner: inner - } +/// Loads and configures ye old NSView for this controller. +extern fn load_view(this: &mut Object, _: Sel) { + unsafe { + let zero: NSRect = Rect::zero().into(); + let view: id = msg_send![register_view_class::(), new]; + let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + let _: () = msg_send![view, setFrame:zero]; + let _: () = msg_send![this, setView:view]; } } -/// This is used for some specific calls, where macOS NSView needs to be -/// forcefully dragged into the modern age (e.g, position coordinates from top left...). -extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL { - return YES; -} - -/// Registers an `NSView` subclass, and configures it to hold some ivars for various things we need -/// to store. -fn register_class() -> *const Class { +/// Registers an `NSViewController`. +pub fn register_controller_class() -> *const Class { static mut VIEW_CLASS: *const Class = 0 as *const Class; static INIT: Once = Once::new(); INIT.call_once(|| unsafe { - let superclass = Class::get("NSView").unwrap(); - let mut decl = ClassDecl::new("SBAView", superclass).unwrap(); - - // Force NSView to render from the top-left, not bottom-left - decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL); + let superclass = Class::get("NSViewController").unwrap(); + let mut decl = ClassDecl::new("RSTViewController", superclass).unwrap(); - // Request optimized backing layers - decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL); + decl.add_ivar::(VIEW_CONTROLLER_PTR); + + // NSViewController + decl.add_method(sel!(loadView), load_view:: as extern fn(&mut Object, _)); VIEW_CLASS = decl.register(); }); diff --git a/appkit/src/view/mod.rs b/appkit/src/view/mod.rs index ac35a3f..a859585 100644 --- a/appkit/src/view/mod.rs +++ b/appkit/src/view/mod.rs @@ -1,2 +1,11 @@ +pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr"; + pub(crate) mod class; +pub(crate) mod controller; + +pub mod traits; +pub use traits::*; + +pub mod view; +pub use view::View; diff --git a/appkit/src/view/traits.rs b/appkit/src/view/traits.rs new file mode 100644 index 0000000..ec456c7 --- /dev/null +++ b/appkit/src/view/traits.rs @@ -0,0 +1,20 @@ +//! Various traits used for Views. + +use objc::runtime::Object; + +use objc_id::ShareId; + +use crate::dragdrop::DragOperation; + +pub trait ViewWrapper { + fn get_handle(&self) -> Option>; +} + +pub trait ViewController { + fn did_load(&self); + + fn dragging_entered(&self) -> DragOperation { DragOperation::None } + fn prepare_for_drag_operation(&self) -> bool { false } + fn perform_drag_operation(&self) -> bool { false } + fn dragging_exited(&self) {} +} diff --git a/appkit/src/view/view.rs b/appkit/src/view/view.rs new file mode 100644 index 0000000..dd7c085 --- /dev/null +++ b/appkit/src/view/view.rs @@ -0,0 +1,71 @@ +//! A wrapper for `NSViewController`. Uses interior mutability to + +use std::rc::Rc; +use std::cell::RefCell; + +use cocoa::base::{id, nil, YES, NO}; +use cocoa::foundation::{NSString, NSArray}; + +use objc_id::ShareId; +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; + +use crate::pasteboard::PasteBoardType; +use crate::view::{VIEW_CONTROLLER_PTR, ViewController}; +use crate::view::controller::register_controller_class; + +#[derive(Default)] +pub struct ViewInner { + pub controller: Option> +} + +impl ViewInner { + pub fn configure(&mut self, controller: &T) { + self.controller = Some(unsafe { + let view_controller: id = msg_send![register_controller_class::(), new]; + (&mut *view_controller).set_ivar(VIEW_CONTROLLER_PTR, controller as *const T as usize); + + let view: id = msg_send![view_controller, view]; + (&mut *view).set_ivar(VIEW_CONTROLLER_PTR, controller as *const T as usize); + + ShareId::from_ptr(view_controller) + }); + } + + pub fn register_for_dragged_types(&self, types: &[PasteBoardType]) { + if let Some(controller) = &self.controller { + unsafe { + let types = NSArray::arrayWithObjects(nil, &types.iter().map(|t| { + t.to_nsstring() + }).collect::>()); + + let view: id = msg_send![*controller, view]; + let _: () = msg_send![view, registerForDraggedTypes:types]; + } + } + } +} + +#[derive(Default)] +pub struct View(Rc>); + +impl View { + pub fn configure(&self, controller: &T) { + { + let mut view = self.0.borrow_mut(); + view.configure(controller); + } + + controller.did_load(); + } + + pub fn get_handle(&self) -> Option> { + let view = self.0.borrow(); + view.controller.clone() + } + + pub fn register_for_dragged_types(&self, types: &[PasteBoardType]) { + let view = self.0.borrow(); + view.register_for_dragged_types(types); + } +} diff --git a/appkit/src/webview/controller.rs b/appkit/src/webview/controller.rs index 3a743d3..058ce4d 100644 --- a/appkit/src/webview/controller.rs +++ b/appkit/src/webview/controller.rs @@ -14,7 +14,7 @@ use objc::declare::ClassDecl; use objc::runtime::{Class, Object, Sel}; use objc::{class, msg_send, sel, sel_impl}; -use crate::ViewController; +use crate::view::ViewController; use crate::view::class::register_view_class; use crate::webview::action::{NavigationAction, NavigationResponse}; use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; @@ -27,7 +27,9 @@ extern fn load_view(this: &mut Object, _: let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR); // Technically private! + #[cfg(feature = "enable-webview-downloading")] let process_pool: id = msg_send![configuration, processPool]; + #[cfg(feature = "enable-webview-downloading")] let _: () = msg_send![process_pool, _setDownloadDelegate:&*this]; let zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1000., 600.)); @@ -42,23 +44,7 @@ extern fn load_view(this: &mut Object, _: // Clean this up to be safe, as WKWebView makes a copy and we don't need it anymore. (*this).set_ivar(WEBVIEW_CONFIG_VAR, nil); - // Note that we put this in a backing NSView to handle an edge case - if someone sets the - // WKWebView as the content view of a window, and the inspector is able to be activated, - // it'll try to (at first) add the inspector to the parent view of the WKWebView... which - // is undefined behavior. - let view: id = msg_send![register_view_class(), new]; - let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; - let _: () = msg_send![view, setFrame:zero]; - let _: () = msg_send![view, addSubview:webview]; - - let constraint = class!(NSLayoutConstraint); - let constraints = NSArray::arrayWithObjects(nil, &vec![ - msg_send![constraint, constraintWithItem:webview attribute:7 relatedBy:0 toItem:view attribute:7 multiplier:1.0 constant:0.0], - msg_send![constraint, constraintWithItem:webview attribute:8 relatedBy:0 toItem:view attribute:8 multiplier:1.0 constant:0.0], - ]); - - let _: () = msg_send![constraint, activateConstraints:constraints]; - let _: () = msg_send![this, setView:view]; + let _: () = msg_send![this, setView:webview]; } } diff --git a/appkit/src/webview/webview.rs b/appkit/src/webview/webview.rs index bddfa77..9098632 100644 --- a/appkit/src/webview/webview.rs +++ b/appkit/src/webview/webview.rs @@ -18,11 +18,9 @@ use objc_id::ShareId; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; -use crate::ViewController; -use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; -use crate::webview::WebViewController; +use crate::view::ViewController; +use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR, WebViewController}; use crate::webview::controller::register_controller_class; - use crate::webview::config::{WebViewConfig, InjectAt}; #[derive(Default)] diff --git a/appkit/src/window/traits.rs b/appkit/src/window/traits.rs index 4468e9a..50a94fa 100644 --- a/appkit/src/window/traits.rs +++ b/appkit/src/window/traits.rs @@ -58,13 +58,7 @@ pub trait WindowWrapper { /// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa /// lifecycle methods, but mix in a few extra things to handle offering configuration tools /// in lieu of subclasses. -pub trait WindowController { - /// `NSWindow` has a lovely usability feature wherein it'll cache the position in - /// `UserDefaults` when a window closes. This is generally nice for a lot of cases (e.g, - /// documents) but needs a key to work with. A blank key, the default, will not cache - so - /// you can implement this and return your own key per window delegate to cache accordingly. - fn autosave_name(&self) -> &str { "" } - +pub trait WindowController { /// The framework offers a standard, modern `NSWindow` by default - but sometimes you want /// something else. Implement this and return your desired Window configuration. fn config(&self) -> WindowConfig { WindowConfig::default() } diff --git a/appkit/src/window/window.rs b/appkit/src/window/window.rs index fdeef31..5ea455e 100644 --- a/appkit/src/window/window.rs +++ b/appkit/src/window/window.rs @@ -5,13 +5,13 @@ use std::rc::Rc; use std::cell::RefCell; use cocoa::base::{id, nil, YES, NO}; -use cocoa::foundation::NSString; +use cocoa::foundation::{NSSize, NSString}; use objc_id::Id; use objc::runtime::Object; use objc::{msg_send, sel, sel_impl}; -use crate::{ViewController, ViewWrapper}; +use crate::view::{ViewController, ViewWrapper}; use crate::toolbar::{Toolbar, ToolbarDelegate}; use crate::window::WindowController; use crate::window::controller::{register_window_controller_class}; @@ -55,8 +55,6 @@ impl WindowInner { /// /// APPKIT! pub fn configure(&mut self, window_controller: &T) { - let autosave_name = window_controller.autosave_name(); - let window = window_controller.config().0; self.controller = Some(unsafe { @@ -67,12 +65,6 @@ impl WindowInner { let window: id = msg_send![controller, window]; let _: () = msg_send![window, setDelegate:controller]; - - // Now we need to make sure to re-apply the NSAutoSaveName, as initWithWindow - // strips it... for some reason. We want it applied as it does nice things like - // save the window position in the Defaults database, which is what users expect. - let autosave = NSString::alloc(nil).init_str(autosave_name); - let _: () = msg_send![window, setFrameAutosaveName:autosave]; Id::from_ptr(controller) }); @@ -126,6 +118,31 @@ impl WindowInner { } } + /// Used for setting this Window autosave name. + pub fn set_autosave_name(&mut self, name: &str) { + if let Some(controller) = &self.controller { + unsafe { + let window: id = msg_send![*controller, window]; + + // Now we need to make sure to re-apply the NSAutoSaveName, as initWithWindow + // strips it... for some reason. We want it applied as it does nice things like + // save the window position in the Defaults database, which is what users expect. + let autosave = NSString::alloc(nil).init_str(name); + let _: () = msg_send![window, setFrameAutosaveName:autosave]; + } + } + } + + pub fn set_minimum_content_size>(&self, width: F, height: F) { + if let Some(controller) = &self.controller { + unsafe { + let size = NSSize::new(width.into(), height.into()); + let window: id = msg_send![*controller, window]; + let _: () = msg_send![window, setMinSize:size]; + } + } + } + /// Used for setting a toolbar on this window. Note that this takes ownership of whatever /// `ToolbarDelegate` you pass! The underlying `NSToolbar` is a bit... old, and it's just /// easier to do things this way. @@ -229,6 +246,18 @@ impl Window { window.set_titlebar_appears_transparent(transparent); } + /// Set the window autosave name, which preserves things like position across restarts. + pub fn set_autosave_name(&self, name: &str) { + let mut window = self.0.borrow_mut(); + window.set_autosave_name(name); + } + + /// Sets the window's smallest size it can shrink to. + pub fn set_minimum_content_size>(&self, width: F, height: F) { + let window = self.0.borrow_mut(); + window.set_minimum_content_size(width, height); + } + /// Sets the Toolbar for this window. Note that this takes ownership of the toolbar! pub fn set_toolbar(&self, identifier: &str, toolbar: T) { let mut window = self.0.borrow_mut();