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.
This commit is contained in:
Ryan McGrath 2020-03-04 18:33:11 -08:00
parent 5cd3a53681
commit 0446227a8d
No known key found for this signature in database
GPG key ID: 811674B62B666830
21 changed files with 605 additions and 144 deletions

View file

47
appkit/src/dragdrop.rs Normal file
View file

@ -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<DragOperation> 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
}
}
}

View file

@ -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<NSInteger> 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); }
}
}
}

View file

@ -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<NSInteger> 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<SearchPathDomainMask> 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<SearchPathDirectory> 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
}
}
}

View file

@ -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<Object>
}
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<String, Box<dyn std::error::Error>> {
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<RefCell<FileManagerInner>>);
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<String, Box<dyn std::error::Error>> {
let manager = self.0.borrow();
manager.get_path(directory, in_domain)
}
//pub fn contents_of(directory: &str, properties: &[
}

View file

@ -1,10 +1,12 @@
pub mod enums; pub mod enums;
pub use enums::*; pub use enums::*;
pub mod manager;
pub use manager::FileManager;
pub mod traits; pub mod traits;
pub use traits::OpenSaveController; pub use traits::*;
pub mod save; pub mod save;
pub use save::FileSavePanel; pub use save::FileSavePanel;

View file

@ -11,7 +11,7 @@ use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::ShareId;
use crate::file_panel::enums::ModalResponse; use crate::filesystem::enums::ModalResponse;
use crate::utils::str_from; use crate::utils::str_from;
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,6 +1,5 @@
//! A trait that you can implement to handle open and save file dialogs. This more or less maps //! A trait that you can implement to handle open and save file dialogs. This more or less maps
//! over to `NSOpenPanel` and `NSSavePanel` handling. //! over to `NSOpenPanel` and `NSSavePanel` handling.
pub trait OpenSaveController { pub trait OpenSaveController {
/// Called when the user has entered a filename (typically, during saving). `confirmed` /// Called when the user has entered a filename (typically, during saving). `confirmed`
/// indicates whether or not they hit the save button. /// 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. /// Determine whether the specified URL should be enabled in the Open panel.
fn should_enable_url(&self, _url: &str) -> bool { true } 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 {
}

39
appkit/src/geometry.rs Normal file
View file

@ -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<Rect> for NSRect {
fn from(rect: Rect) -> NSRect {
NSRect::new(
NSPoint::new(rect.top, rect.left),
NSSize::new(rect.width, rect.height)
)
}
}

View file

@ -20,27 +20,23 @@ pub use objc_id::ShareId;
pub use objc::runtime::Object; pub use objc::runtime::Object;
pub use cocoa::base::id; pub use cocoa::base::id;
pub trait ViewWrapper {
fn get_handle(&self) -> Option<ShareId<Object>>;
}
pub trait ViewController {
fn did_load(&self);
}
pub mod alert; pub mod alert;
pub mod app; pub mod app;
pub mod events;
pub mod menu;
pub mod button; pub mod button;
pub mod file_panel; pub mod collection_view;
pub mod toolbar; pub mod dragdrop;
pub mod notifications; pub mod events;
pub mod webview; pub mod filesystem;
pub mod view; pub mod geometry;
pub mod window; pub mod menu;
pub mod networking; pub mod networking;
pub mod notifications;
pub mod pasteboard;
pub mod toolbar;
pub mod utils; pub mod utils;
pub mod view;
pub mod webview;
pub mod window;
pub mod prelude { pub mod prelude {
pub use crate::app::{App, AppDelegate}; pub use crate::app::{App, AppDelegate};
@ -59,7 +55,7 @@ pub mod prelude {
WebView, WebViewConfig, WebViewController WebView, WebViewConfig, WebViewController
}; };
pub use crate::{ViewController, ViewWrapper}; pub use crate::view::{View, ViewController, ViewWrapper};
pub use appkit_derive::{ pub use appkit_derive::{
WindowWrapper WindowWrapper

79
appkit/src/pasteboard.rs Normal file
View file

@ -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",
})
}
}
}

View file

@ -9,12 +9,16 @@
use std::sync::Once; 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::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, msg_send, sel, sel_impl}; 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. /// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL { extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
return YES; 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<T: ViewController>(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<T: ViewController>(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<T: ViewController>(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<T: ViewController>(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 /// Injects an `NSView` subclass, with some callback and pointer ivars for what we
/// need to do. /// need to do.
pub(crate) fn register_view_class() -> *const Class { pub(crate) fn register_view_class<T: ViewController>() -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class; static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new(); static INIT: Once = Once::new();
@ -41,10 +89,19 @@ pub(crate) fn register_view_class() -> *const Class {
let superclass = Class::get("NSView").unwrap(); let superclass = Class::get("NSView").unwrap();
let mut decl = ClassDecl::new("RSTView", superclass).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::<usize>(VIEW_CONTROLLER_PTR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL); 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!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _)); 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::<T> as extern fn (&mut Object, _, _) -> NSUInteger);
decl.add_method(sel!(prepareForDragOperation:), prepare_for_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
decl.add_method(sel!(performDragOperation:), perform_drag_operation::<T> as extern fn (&mut Object, _, _) -> BOOL);
decl.add_method(sel!(draggingExited:), dragging_exited::<T> as extern fn (&mut Object, _, _));
VIEW_CLASS = decl.register(); VIEW_CLASS = decl.register();
}); });

View file

@ -1,69 +1,43 @@
//! Hoists a basic NSView. In our current particular use case, //! Hoists a basic `NSViewController`. We use `NSViewController` rather than plain `NSView` as
//! this is primarily used as the ContentView for a window. From there, //! we're interested in the lifecycle methods and events.
//! we configure an NSToolbar and WKWebview on top of them.
use std::sync::Once; use std::sync::Once;
use cocoa::base::{id, YES}; use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::{NSRect, NSPoint, NSSize}; use cocoa::foundation::{NSRect, NSUInteger};
use objc_id::Id;
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::{Class, Object, Sel};
use objc::{msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
/// A trait for handling the view lifecycle. use crate::geometry::Rect;
pub trait View { use crate::view::{VIEW_CONTROLLER_PTR, ViewController};
fn did_load(&mut self) {} use crate::view::class::register_view_class;
}
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime /// Loads and configures ye old NSView for this controller.
/// where our `NSWindow` and associated delegate live. extern fn load_view<T: ViewController>(this: &mut Object, _: Sel) {
pub struct View { unsafe {
pub inner: Id<Object> let zero: NSRect = Rect::zero().into();
} let view: id = msg_send![register_view_class::<T>(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
impl View { let _: () = msg_send![view, setFrame:zero];
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance), let _: () = msg_send![this, setView:view];
/// 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
}
} }
} }
/// This is used for some specific calls, where macOS NSView needs to be /// Registers an `NSViewController`.
/// forcefully dragged into the modern age (e.g, position coordinates from top left...). pub fn register_controller_class<T: ViewController + 'static>() -> *const Class {
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 {
static mut VIEW_CLASS: *const Class = 0 as *const Class; static mut VIEW_CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| unsafe { INIT.call_once(|| unsafe {
let superclass = Class::get("NSView").unwrap(); let superclass = Class::get("NSViewController").unwrap();
let mut decl = ClassDecl::new("SBAView", superclass).unwrap(); let mut decl = ClassDecl::new("RSTViewController", 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);
// Request optimized backing layers decl.add_ivar::<usize>(VIEW_CONTROLLER_PTR);
decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
// NSViewController
decl.add_method(sel!(loadView), load_view::<T> as extern fn(&mut Object, _));
VIEW_CLASS = decl.register(); VIEW_CLASS = decl.register();
}); });

View file

@ -1,2 +1,11 @@
pub(crate) static VIEW_CONTROLLER_PTR: &str = "rstViewControllerPtr";
pub(crate) mod class; pub(crate) mod class;
pub(crate) mod controller;
pub mod traits;
pub use traits::*;
pub mod view;
pub use view::View;

20
appkit/src/view/traits.rs Normal file
View file

@ -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<ShareId<Object>>;
}
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) {}
}

71
appkit/src/view/view.rs Normal file
View file

@ -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<ShareId<Object>>
}
impl ViewInner {
pub fn configure<T: ViewController + 'static>(&mut self, controller: &T) {
self.controller = Some(unsafe {
let view_controller: id = msg_send![register_controller_class::<T>(), 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::<Vec<id>>());
let view: id = msg_send![*controller, view];
let _: () = msg_send![view, registerForDraggedTypes:types];
}
}
}
}
#[derive(Default)]
pub struct View(Rc<RefCell<ViewInner>>);
impl View {
pub fn configure<T: ViewController + 'static>(&self, controller: &T) {
{
let mut view = self.0.borrow_mut();
view.configure(controller);
}
controller.did_load();
}
pub fn get_handle(&self) -> Option<ShareId<Object>> {
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);
}
}

View file

@ -14,7 +14,7 @@ use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel}; use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::ViewController; use crate::view::ViewController;
use crate::view::class::register_view_class; use crate::view::class::register_view_class;
use crate::webview::action::{NavigationAction, NavigationResponse}; use crate::webview::action::{NavigationAction, NavigationResponse};
use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR};
@ -27,7 +27,9 @@ extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _:
let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR); let configuration: id = *this.get_ivar(WEBVIEW_CONFIG_VAR);
// Technically private! // Technically private!
#[cfg(feature = "enable-webview-downloading")]
let process_pool: id = msg_send![configuration, processPool]; let process_pool: id = msg_send![configuration, processPool];
#[cfg(feature = "enable-webview-downloading")]
let _: () = msg_send![process_pool, _setDownloadDelegate:&*this]; let _: () = msg_send![process_pool, _setDownloadDelegate:&*this];
let zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1000., 600.)); let zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(1000., 600.));
@ -42,23 +44,7 @@ extern fn load_view<T: ViewController + WebViewController>(this: &mut Object, _:
// Clean this up to be safe, as WKWebView makes a copy and we don't need it anymore. // 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); (*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 let _: () = msg_send![this, setView:webview];
// 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];
} }
} }

View file

@ -18,11 +18,9 @@ use objc_id::ShareId;
use objc::runtime::Object; use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::ViewController; use crate::view::ViewController;
use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR}; use crate::webview::{WEBVIEW_VAR, WEBVIEW_CONFIG_VAR, WEBVIEW_CONTROLLER_PTR, WebViewController};
use crate::webview::WebViewController;
use crate::webview::controller::register_controller_class; use crate::webview::controller::register_controller_class;
use crate::webview::config::{WebViewConfig, InjectAt}; use crate::webview::config::{WebViewConfig, InjectAt};
#[derive(Default)] #[derive(Default)]

View file

@ -58,13 +58,7 @@ pub trait WindowWrapper {
/// Lifecycle events for anything that `impl Window`'s. These map to the standard Cocoa /// 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 /// lifecycle methods, but mix in a few extra things to handle offering configuration tools
/// in lieu of subclasses. /// in lieu of subclasses.
pub trait WindowController { 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 { "" }
/// The framework offers a standard, modern `NSWindow` by default - but sometimes you want /// The framework offers a standard, modern `NSWindow` by default - but sometimes you want
/// something else. Implement this and return your desired Window configuration. /// something else. Implement this and return your desired Window configuration.
fn config(&self) -> WindowConfig { WindowConfig::default() } fn config(&self) -> WindowConfig { WindowConfig::default() }

View file

@ -5,13 +5,13 @@ use std::rc::Rc;
use std::cell::RefCell; use std::cell::RefCell;
use cocoa::base::{id, nil, YES, NO}; use cocoa::base::{id, nil, YES, NO};
use cocoa::foundation::NSString; use cocoa::foundation::{NSSize, NSString};
use objc_id::Id; use objc_id::Id;
use objc::runtime::Object; use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use crate::{ViewController, ViewWrapper}; use crate::view::{ViewController, ViewWrapper};
use crate::toolbar::{Toolbar, ToolbarDelegate}; use crate::toolbar::{Toolbar, ToolbarDelegate};
use crate::window::WindowController; use crate::window::WindowController;
use crate::window::controller::{register_window_controller_class}; use crate::window::controller::{register_window_controller_class};
@ -55,8 +55,6 @@ impl WindowInner {
/// ///
/// APPKIT! /// APPKIT!
pub fn configure<T: WindowController + 'static>(&mut self, window_controller: &T) { pub fn configure<T: WindowController + 'static>(&mut self, window_controller: &T) {
let autosave_name = window_controller.autosave_name();
let window = window_controller.config().0; let window = window_controller.config().0;
self.controller = Some(unsafe { self.controller = Some(unsafe {
@ -67,12 +65,6 @@ impl WindowInner {
let window: id = msg_send![controller, window]; let window: id = msg_send![controller, window];
let _: () = msg_send![window, setDelegate:controller]; 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) 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<F: Into<f64>>(&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 /// 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 /// `ToolbarDelegate` you pass! The underlying `NSToolbar` is a bit... old, and it's just
/// easier to do things this way. /// easier to do things this way.
@ -229,6 +246,18 @@ impl Window {
window.set_titlebar_appears_transparent(transparent); 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<F: Into<f64>>(&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! /// Sets the Toolbar for this window. Note that this takes ownership of the toolbar!
pub fn set_toolbar<T: ToolbarDelegate + 'static>(&self, identifier: &str, toolbar: T) { pub fn set_toolbar<T: ToolbarDelegate + 'static>(&self, identifier: &str, toolbar: T) {
let mut window = self.0.borrow_mut(); let mut window = self.0.borrow_mut();