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:
parent
5cd3a53681
commit
0446227a8d
0
appkit/src/collection_view/mod.rs
Normal file
0
appkit/src/collection_view/mod.rs
Normal file
47
appkit/src/dragdrop.rs
Normal file
47
appkit/src/dragdrop.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
}
|
115
appkit/src/filesystem/enums.rs
Normal file
115
appkit/src/filesystem/enums.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
69
appkit/src/filesystem/manager.rs
Normal file
69
appkit/src/filesystem/manager.rs
Normal 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: &[
|
||||
}
|
|
@ -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;
|
|
@ -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)]
|
|
@ -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 {
|
||||
|
||||
}
|
39
appkit/src/geometry.rs
Normal file
39
appkit/src/geometry.rs
Normal 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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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<ShareId<Object>>;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
79
appkit/src/pasteboard.rs
Normal file
79
appkit/src/pasteboard.rs
Normal 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",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<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
|
||||
/// 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 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::<usize>(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::<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();
|
||||
});
|
||||
|
|
|
@ -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<Object>
|
||||
}
|
||||
|
||||
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<T: ViewController>(this: &mut Object, _: Sel) {
|
||||
unsafe {
|
||||
let zero: NSRect = 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];
|
||||
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<T: ViewController + 'static>() -> *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::<usize>(VIEW_CONTROLLER_PTR);
|
||||
|
||||
// NSViewController
|
||||
decl.add_method(sel!(loadView), load_view::<T> as extern fn(&mut Object, _));
|
||||
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
20
appkit/src/view/traits.rs
Normal file
20
appkit/src/view/traits.rs
Normal 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
71
appkit/src/view/view.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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<T: ViewController + WebViewController>(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<T: ViewController + WebViewController>(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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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() }
|
||||
|
|
|
@ -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<T: WindowController + 'static>(&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<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
|
||||
/// `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<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!
|
||||
pub fn set_toolbar<T: ToolbarDelegate + 'static>(&self, identifier: &str, toolbar: T) {
|
||||
let mut window = self.0.borrow_mut();
|
||||
|
|
Loading…
Reference in a new issue