Ongoing documentation and cleanup work.

This commit is contained in:
Ryan McGrath 2021-03-15 17:09:50 -07:00
parent bc54b49475
commit 696907aa73
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
30 changed files with 702 additions and 222 deletions

View file

@ -24,8 +24,12 @@ use crate::macos::FocusRingType;
/// where our `NSButton` lives.
#[derive(Debug)]
pub struct Button {
/// A handle for the underlying Objective-C object.
pub objc: ShareId<Object>,
/// A reference to an image, if set. We keep a copy to avoid any ownership snafus.
pub image: Option<Image>,
handler: Option<TargetActionHandler>,
/// A pointer to the Objective-C runtime top layout constraint.
@ -60,7 +64,11 @@ impl Button {
let title = NSString::new(text);
let view: id = unsafe {
let button: id = msg_send![register_class(), buttonWithTitle:&*title target:nil action:nil];
let button: id = msg_send![register_class(), buttonWithTitle:&*title
target:nil
action:nil
];
let _: () = msg_send![button, setWantsLayer:YES];
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
button
@ -81,6 +89,7 @@ impl Button {
}
}
/// Sets an image on the underlying button.
pub fn set_image(&mut self, image: Image) {
unsafe {
let _: () = msg_send![&*self.objc, setImage:&*image.0];
@ -117,6 +126,8 @@ impl Button {
}
}
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
/// button will fire.
pub fn set_key_equivalent(&self, key: &str) {
let key = NSString::new(key);
@ -241,19 +252,47 @@ fn register_class() -> *const Class {
#[cfg(feature = "macos")]
#[derive(Debug)]
pub enum BezelStyle {
/// A standard circular button.
Circular,
/// A standard disclosure style button.
Disclosure,
/// The standard looking "Help" (?) button.
HelpButton,
/// An inline style, varies across OS's.
Inline,
/// A recessed style, varies slightly across OS's.
Recessed,
/// A regular square style, with no special styling.
RegularSquare,
/// A standard rounded rectangle.
RoundRect,
/// A standard rounded button.
Rounded,
/// A standard rounded disclosure button.
RoundedDisclosure,
/// A shadowless square styl.e
ShadowlessSquare,
/// A small square style.
SmallSquare,
/// A textured rounded style.
TexturedRounded,
/// A textured square style.
TexturedSquare,
/// Any style that's not known by this framework (e.g, if Apple
/// introduces something new).
Unknown(NSUInteger)
}

View file

@ -3,12 +3,22 @@
use crate::foundation::NSUInteger;
/// Flags that indicate a key is in the mix for an event.
#[derive(Clone, Copy, Debug)]
pub enum EventModifierFlag {
/// CapsLock (or shift... oddly named...) is held.
CapsLock,
/// Control is held.
Control,
/// Option is held.
Option,
/// Command (CMD) is held.
Command,
/// Device independent flags mask.
DeviceIndependentFlagsMask
}
@ -36,7 +46,9 @@ impl From<&EventModifierFlag> for NSUInteger {
}
}
/// Represents an event type that you can request to be notified about.
#[derive(Clone, Copy, Debug)]
pub enum EventType {
/// A keydown event.
KeyDown
}

View file

@ -2,15 +2,31 @@
use crate::foundation::{NSInteger, NSUInteger};
/// Represents a modal response for macOS modal dialogs.
#[derive(Copy, Clone, Debug)]
pub enum ModalResponse {
/// The user hit the "Ok" button.
Ok,
/// Continue.
Continue,
/// Canceled.
Canceled,
/// Stopped.
Stopped,
/// Aborted.
Aborted,
/// The first button in the dialog was clicked.
FirstButtonReturned,
/// The second button in the dialog was clicked.
SecondButtonReturned,
/// The third button in the dialog was clicked.
ThirdButtonReturned
}
@ -34,12 +50,22 @@ impl From<NSInteger> for ModalResponse {
}
}
/// Represents a type of search path used in file manager calls.
#[derive(Copy, Clone, Debug)]
pub enum SearchPathDomainMask {
/// User files and folders.
User,
/// Local volume files and folders.
Local,
/// Netowrk files and folders.
Network,
/// Search all domains. Not typically used these days.
Domain,
/// Search all domains. Not typically used these days.
AllDomains
}
@ -55,34 +81,95 @@ impl From<SearchPathDomainMask> for NSUInteger {
}
}
/// Represents a type of search path to use.
///
/// This enum is particularly useful for applications that need to exist both inside and outside of
/// the sandbox. For example: `SearchPathDirectory::Documents` will find the standard `Documents`
/// directory outside of the sandbox, but use the sandbox `Documents` directory in sandboxed
/// applications.
#[derive(Copy, Clone, Debug)]
pub enum SearchPathDirectory {
/// The applications folder.
Applications,
/// Unsupported applications and demo versions. Not generally used these days.
DemoApplications,
/// Developer applications (_/Developer/Applications_). Not generally used these days.
DeveloperApplications,
/// System and network admin apps.
AdminApplications,
/// User-visible docs, support, and config files.
Library,
/// Dev resources. (_/Developer_)
Developer,
/// User home directories. (_/Users_)
User,
/// Documentation.
Documentation,
/// Documents directory.
Documents,
/// Core Services (_/System/Library/CoreServices_)
CoreServices,
/// User's autosaved documents (_/Library/Autosave Information_)
AutosavedInformation,
/// The current user's Desktop directory.
Desktop,
/// Discardable cache files. (_/Library/Caches_)
Caches,
/// App support files (_/Library/Application Support_)
ApplicationSupport,
/// The curent user's Downloads directory.
Downloads,
/// Input methods (_/Library/Input Methods_)
InputMethods,
/// The current user's Movies directory.
Movies,
/// The current user's Music directory.
Music,
/// The current user's pictures directory.
Pictures,
/// System PPD files (_/Library/Printers/PPDs_)
PrinterDescription,
/// The current user's public sharing directory.
SharedPublic,
/// The Preferences Pane directory, where system preferences files live.
/// (_/Library/PreferencePanes_)
PreferencePanes,
/// The user scripts folder for the calling application
/// (_~/Library/Application Scripts/<code-signing-id>_).
ApplicationScripts,
/// Constant used in creating a temp directory.
ItemReplacement,
/// All directories where apps can be stored.
AllApplications,
/// All directories where resources can be stored.
AllLibraries,
/// The Trash directory.
Trash
}
@ -112,7 +199,6 @@ impl From<SearchPathDirectory> for NSUInteger {
SearchPathDirectory::SharedPublic => 21,
SearchPathDirectory::PreferencePanes => 22,
SearchPathDirectory::ApplicationScripts => 23,
SearchPathDirectory::ItemReplacement => 99,
SearchPathDirectory::AllApplications => 100,
SearchPathDirectory::AllLibraries => 101,

View file

@ -2,7 +2,7 @@
//! tricky, and this transparently handles it for you).
use std::error::Error;
use std::sync::RwLock;
use std::sync::{Arc, RwLock};
use objc_id::Id;
use objc::runtime::{BOOL, Object};
@ -10,47 +10,51 @@ use objc::{class, msg_send, sel, sel_impl};
use url::Url;
use crate::foundation::{id, nil, NO, NSString, NSUInteger};
use crate::error::{Error as AppKitError};
use crate::error::Error as AppKitError;
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
#[derive(Debug)]
pub struct FileManager {
pub manager: RwLock<Id<Object>>
}
/// A FileManager can be used for file operations (moving files, etc).
///
/// If your app is not sandboxed, you can use your favorite Rust library -
/// but if you _are_ operating in the sandbox, there's a good chance you'll want to use this.
///
/// @TODO: Couldn't this just be a ShareId?
#[derive(Clone, Debug)]
pub struct FileManager(pub Arc<RwLock<Id<Object>>>);
impl Default for FileManager {
/// Returns a default file manager, which maps to the default system file manager. For common
/// and simple tasks, with no callbacks, you might want this.
fn default() -> Self {
FileManager {
manager: RwLock::new(unsafe {
FileManager(Arc::new(RwLock::new(unsafe {
let manager: id = msg_send![class!(NSFileManager), defaultManager];
Id::from_ptr(manager)
})
}
})))
}
}
impl FileManager {
/// Returns a new FileManager that opts in to delegate methods.
pub fn new() -> Self {
FileManager {
manager: RwLock::new(unsafe {
FileManager(Arc::new(RwLock::new(unsafe {
let manager: id = msg_send![class!(NSFileManager), new];
Id::from_ptr(manager)
})
}
})))
}
/// Given a directory/domain combination, will attempt to get the directory that matches.
/// Returns a PathBuf that wraps the given location. If there's an error on the Objective-C
/// side, we attempt to catch it and bubble it up.
pub fn get_directory(&self, directory: SearchPathDirectory, in_domain: SearchPathDomainMask) -> Result<Url, Box<dyn Error>> {
pub fn get_directory(
&self,
directory: SearchPathDirectory,
in_domain: SearchPathDomainMask
) -> Result<Url, Box<dyn Error>> {
let dir: NSUInteger = directory.into();
let mask: NSUInteger = in_domain.into();
let directory = unsafe {
let manager = self.manager.read().unwrap();
let manager = self.0.read().unwrap();
let dir: id = msg_send![&**manager, URLForDirectory:dir
inDomain:mask
appropriateForURL:nil
@ -76,7 +80,7 @@ impl FileManager {
// This should potentially be write(), but the backing class handles this logic
// already, so... going to leave it as read.
let manager = self.manager.read().unwrap();
let manager = self.0.read().unwrap();
let error: id = nil;
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];

View file

@ -47,12 +47,23 @@ impl FileSavePanel {
}
}
/// @TODO: Do we even need this?
pub fn set_delegate(&mut self) {}
pub fn set_suggested_filename(&mut self, suggested_filename: &str) {
/// Sets a suggested filename for the save dialog. The user can still change this if they
/// choose to, but it's generally best practice to call this.
pub fn set_suggested_filename<S: AsRef<str>>(&mut self, suggested_filename: S) {
unsafe {
let filename = NSString::new(suggested_filename);
let _: () = msg_send![&*self.panel, setNameFieldStringValue:filename];
let filename = NSString::new(suggested_filename.as_ref());
let _: () = msg_send![&*self.panel, setNameFieldStringValue:&*filename];
}
}
/// Set the message text displayed in the panel.
pub fn set_message<S: AsRef<str>>(&mut self, message: S) {
unsafe {
let message = NSString::new(message.as_ref());
let _: () = msg_send![&*self.panel, setMessage:&*message];
}
}

View file

@ -82,6 +82,14 @@ impl FileSelectPanel {
self.can_choose_files = can_choose;
}
/// Set the message text displayed in the panel.
pub fn set_message<S: AsRef<str>>(&mut self, message: S) {
unsafe {
let message = NSString::new(message.as_ref());
let _: () = msg_send![&*self.panel, setMessage:&*message];
}
}
/// Sets whether the user can choose directories.
pub fn set_can_choose_directories(&mut self, can_choose: bool) {
unsafe {

View file

@ -17,6 +17,7 @@ const UTF8_ENCODING: usize = 4;
/// side is fairly battle tested.
#[derive(Debug)]
pub struct NSString<'a> {
/// A reference to the backing `NSString`.
pub objc: Id<Object>,
phantom: PhantomData<&'a ()>
}

View file

@ -15,11 +15,19 @@ use crate::foundation::{id, YES, NO, NSString};
use crate::utils::os;
use super::icons::*;
#[derive(Debug)]
/// Specifies resizing behavior for image drawing.
#[derive(Copy, Clone, Debug)]
pub enum ResizeBehavior {
/// Fit to the aspect ratio.
AspectFit,
/// Fill the aspect ratio.
AspectFill,
/// Stretch as necessary.
Stretch,
/// Center and then let whatever else flow around it.
Center
}
@ -42,6 +50,8 @@ fn min_cgfloat(x: CGFloat, y: CGFloat) -> CGFloat {
}
impl ResizeBehavior {
/// Given a source and target rectangle, configures and returns a new rectangle configured with
/// the resizing properties of this enum.
pub fn apply(&self, source: CGRect, target: CGRect) -> CGRect {
// if equal, just return source
if
@ -94,13 +104,22 @@ impl ResizeBehavior {
}
}
#[derive(Debug)]
/// A config object that specifies how drawing into an image context should scale.
#[derive(Copy, Clone, Debug)]
pub struct DrawConfig {
/// The size of the source.
pub source: (CGFloat, CGFloat),
/// The size of the target. This may be the same as the source; if not, the source will be
/// scaled to this size.
pub target: (CGFloat, CGFloat),
/// The type of resizing to use during drawing and scaling.
pub resize: ResizeBehavior
}
/// Wraps `NSImage` on macOS, and `UIImage` on iOS and tvOS. Can be used to display images, icons,
/// and so on.
#[derive(Clone, Debug)]
pub struct Image(pub ShareId<Object>);
@ -195,7 +214,11 @@ impl Image {
let block = block.copy();
Image(unsafe {
let img: id = msg_send![class!(NSImage), imageWithSize:target_frame.size flipped:YES drawingHandler:block];
let img: id = msg_send![class!(NSImage), imageWithSize:target_frame.size
flipped:YES
drawingHandler:block
];
ShareId::from_ptr(img)
})
}

View file

@ -223,6 +223,7 @@ impl<T> TextField<T> {
}
}
/// The the text alignment style for this control.
pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe {
let alignment: NSInteger = alignment.into();
@ -232,7 +233,7 @@ impl<T> TextField<T> {
/// Sets the font for this input.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref();
let font = font.as_ref().clone();
unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font];

View file

@ -208,7 +208,12 @@ impl From<NSUInteger> for LayoutFormat {
/// Specifies layout priority.
#[derive(Debug)]
pub enum LayoutPriority {
/// Highest priority.
Required,
/// High priority. Will bend if absolutely necessary.
High,
/// Low priority.
Low
}

View file

@ -42,9 +42,15 @@ impl Into<NSUInteger> for RowAnimation {
}
}
/// Specifies a row edge.
///
/// Generally used to indicate where row actions (swipe-to-reveal) should appear.
#[derive(Copy, Clone, Debug)]
pub enum RowEdge {
/// The leading edge.
Leading,
/// The trailing edge.
Trailing
}

View file

@ -18,7 +18,7 @@ use crate::macos::menu::{Menu, MenuItem};
use crate::foundation::{load_or_register_class, id, nil, YES, NO, NSArray, NSInteger, NSUInteger};
use crate::dragdrop::DragInfo;
use crate::listview::{
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
LISTVIEW_DELEGATE_PTR,
ListViewDelegate, RowEdge
};
use crate::utils::load;
@ -197,7 +197,6 @@ pub(crate) fn register_listview_class() -> *const Class {
pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instance: &T) -> *const Class {
load_or_register_class("NSTableView", instance.subclass_name(), |decl| unsafe {
decl.add_ivar::<usize>(LISTVIEW_DELEGATE_PTR);
decl.add_ivar::<usize>(LISTVIEW_CELL_VENDOR_PTR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);

View file

@ -53,7 +53,8 @@ use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::scrollview::ScrollView;
use crate::utils::CGSize;
use crate::utils::{os, CellFactory, CGSize};
use crate::view::ViewDelegate;
#[cfg(target_os = "macos")]
use crate::macos::menu::MenuItem;
@ -83,7 +84,6 @@ mod actions;
pub use actions::{RowAction, RowActionStyle};
pub(crate) static LISTVIEW_DELEGATE_PTR: &str = "rstListViewDelegatePtr";
pub(crate) static LISTVIEW_CELL_VENDOR_PTR: &str = "rstListViewCellVendorPtr";
use std::any::Any;
use std::sync::{Arc, RwLock};
@ -91,57 +91,6 @@ use std::sync::{Arc, RwLock};
use std::rc::Rc;
use std::cell::RefCell;
use crate::view::ViewDelegate;
pub(crate) type CellFactoryMap = HashMap<&'static str, Box<dyn Fn() -> Box<dyn Any>>>;
#[derive(Clone)]
pub struct CellFactory(pub Rc<RefCell<CellFactoryMap>>);
impl std::fmt::Debug for CellFactory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CellFactory").finish()
}
}
impl CellFactory {
pub fn new() -> Self {
CellFactory(Rc::new(RefCell::new(HashMap::new())))
}
pub fn insert<F, T>(&self, identifier: &'static str, vendor: F)
where
F: Fn() -> T + 'static,
T: ViewDelegate + 'static
{
let mut lock = self.0.borrow_mut();
lock.insert(identifier, Box::new(move || {
let cell = vendor();
Box::new(cell) as Box<dyn Any>
}));
}
pub fn get<R>(&self, identifier: &'static str) -> Box<R>
where
R: ViewDelegate + 'static
{
let lock = self.0.borrow();
let vendor = match lock.get(identifier) {
Some(v) => v,
None => {
panic!("Unable to dequeue cell of type {}: did you forget to register it?", identifier);
}
};
let view = vendor();
if let Ok(view) = view.downcast::<R>() {
view
} else {
panic!("Asking for cell of type {}, but failed to match the type!", identifier);
}
}
}
/// A helper method for instantiating view classes and applying default settings to them.
fn common_init(class: *const Class) -> id {
unsafe {
@ -232,7 +181,7 @@ pub struct ListView<T = ()> {
/// allocation and reuse, which is necessary for an "infinite" listview.
cell_factory: CellFactory,
pub menu: PropertyNullable<Vec<MenuItem>>,
menu: PropertyNullable<Vec<MenuItem>>,
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
@ -277,7 +226,7 @@ impl Default for ListView {
}
impl ListView {
/// Returns a default `View`, suitable for
/// @TODO: The hell is this for?
pub fn new() -> Self {
let class = register_listview_class();
let view = common_init(class);
@ -333,9 +282,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let delegate_ptr: *const T = &*delegate;
let cell_vendor_ptr: *const RefCell<CellFactoryMap> = &*cell.0;
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, delegate_ptr as usize);
(&mut *view).set_ivar(LISTVIEW_CELL_VENDOR_PTR, cell_vendor_ptr as usize);
let _: () = msg_send![view, setDelegate:view];
let _: () = msg_send![view, setDataSource:view];
};
@ -446,13 +393,23 @@ impl<T> ListView<T> {
}
}
/// Style
/// Sets the style for the underlying NSTableView. This property is only supported on macOS
/// 11.0+, and will always be `FullWidth` on anything older.
#[cfg(feature = "macos")]
pub fn set_style(&self, style: crate::foundation::NSInteger) {
if os::is_minimum_version(11) {
unsafe {
let _: () = msg_send![&*self.objc, setStyle:style];
}
}
}
/// Set whether this control can appear with no row selected.
///
/// This defaults to `true`, but some macOS pieces (e.g, a sidebar) may want this set to
/// `false`. This can be particularly useful when implementing a Source List style sidebar
/// view for navigation purposes.
#[cfg(feature = "macos")]
pub fn set_allows_empty_selection(&self, allows: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setAllowsEmptySelection:match allows {
@ -462,12 +419,14 @@ impl<T> ListView<T> {
}
}
/// Set the selection highlight style.
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
unsafe {
let _: () = msg_send![&*self.objc, setSelectionHighlightStyle:style];
}
}
/// Select the rows at the specified indexes, optionally adding to any existing selections.
pub fn select_row_indexes(&self, indexes: &[usize], extends_existing: bool) {
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
@ -483,6 +442,16 @@ impl<T> ListView<T> {
}
}
/// This method should be used when inserting or removing multiple rows at once. Under the
/// hood, it batches the changes and tries to ensure things are done properly. The provided
/// `ListView` for the handler is your `ListView`, and you can call `insert_rows`,
/// `reload_rows`, or `remove_rows` from there.
///
/// ```rust,no_run
/// list_view.perform_batch_updates(|listview| {
/// listview.insert_rows(&[0, 2], RowAnimation::SlideDown);
/// });
/// ```
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
#[cfg(target_os = "macos")]
unsafe {
@ -495,6 +464,11 @@ impl<T> ListView<T> {
}
}
/// Insert new rows at the specified indexes, with the specified animation.
///
/// Your underlying data store must be updated *before* calling this. If inserting multiple
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
/// optimize things accordingly.
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
#[cfg(target_os = "macos")]
unsafe {
@ -514,6 +488,7 @@ impl<T> ListView<T> {
}
}
/// Reload the rows at the specified indexes.
pub fn reload_rows(&self, indexes: &[usize]) {
#[cfg(target_os = "macos")]
unsafe {
@ -532,6 +507,11 @@ impl<T> ListView<T> {
}
}
/// Remove rows at the specified indexes, with the specified animation.
///
/// Your underlying data store must be updated *before* calling this. If removing multiple
/// rows at once, you should also run this inside a `perform_batch_updates` call, as that will
/// optimize things accordingly.
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
#[cfg(target_os = "macos")]
unsafe {
@ -612,16 +592,43 @@ impl<T> ListView<T> {
}
}
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
/// calls yourself, but often easier to implement.
///
/// Calling this will reload (and redraw) your listview based on whatever the data source
/// reports back.
pub fn reload(&self) {
unsafe {
let _: () = msg_send![&*self.objc, reloadData];
}
}
/// Returns the selected row.
pub fn get_selected_row_index(&self) -> NSInteger {
unsafe { msg_send![&*self.objc, selectedRow] }
}
/// Returns the currently clicked row. This is macOS-specific, and is generally used in context
/// menu generation to determine what item the context menu should be for. If the clicked area
/// is not an actual row, this will return `-1`.
///
/// For example (minus the other necessary ListViewDelegate pieces):
///
/// ```rust,no_run
/// impl ListViewDelegate for MyListView {
/// fn context_menu(&self) -> Vec<MenuItem> {
/// let clicked_row = self.list_view.get_clicked_row_index();
///
/// // You could treat this as a "new" menu.
/// if clicked_row == -1 {
/// return vec![];
/// }
///
/// // User right-clicked on a row, so let's show an edit menu.
/// vec![MenuItem::new("Edit")]
/// }
/// }
/// ```
pub fn get_clicked_row_index(&self) -> NSInteger {
unsafe { msg_send![&*self.objc, clickedRow] }
}

View file

@ -83,10 +83,19 @@ fn shared_application<F: Fn(id)>(handler: F) {
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
/// application.
pub struct App<T = (), M = ()> {
/// The underlying Objective-C Object.
pub objc: Id<Object>,
/// The underlying Objective-C Object, which in this case is a delegate that forwards to the
/// app delegate.
pub objc_delegate: Id<Object>,
/// The stored `AppDelegate`.
pub delegate: Box<T>,
/// The main-thread AutoReleasePool. Drains on app exit.
pub pool: AutoReleasePool,
_message: std::marker::PhantomData<M>
}

View file

@ -1,34 +1,91 @@
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::id;
use crate::foundation::{id, YES, NO};
/// Represents a type of cursor that you can associate with mouse movement.
/// @TODO: Loading?
#[derive(Debug)]
pub enum CursorType {
/// A standard arrow.
Arrow,
/// A crosshair.
Crosshair,
/// A closed hand, typically for mousedown and drag.
ClosedHand,
/// An open hand, typically for indicating draggable.
OpenHand,
/// A pointing hand, like clicking a link.
PointingHand,
/// Indicator that something can be resized to the left.
ResizeLeft,
/// Indicator that something can be resized to the right.
ResizeRight,
/// Indicator that something can be resized on the horizontal axis.
ResizeLeftRight,
/// Indicates that something can be resized up.
ResizeUp,
/// Indicates that something can be resized down.
ResizeDown,
/// Indicator that something can be resized on the vertical axis.
ResizeUpDown,
/// Otherwise known as the "poof" or "cloud" cursor. Indicates something will vanish, like
/// dragging into the Trash.
DisappearingItem,
/// Indicate an insertion point, like for text.
IBeam,
/// The vertical version of `CursorType::IBeam`.
IBeamVertical,
/// Indicates an operation is illegal.
OperationNotAllowed,
/// The drag link cursor.
DragLink,
/// Used for drag-and-drop usually, will displayu the standard "+" icon next to the cursor.
DragCopy,
/// Indicates a context menu will open.
ContextMenu
}
/// A wrapper around NSCursor.
///
/// You use then when you need to control how the cursor (pointer) should appear. Like `NSCursor`,
/// this is stack based - you push, and you pop. You are responsible for ensuring that this is
/// correctly popped!
///
/// For a very abbreviated example:
///
/// ```rust,no_run
/// impl ViewDelegate for MyView {
/// fn dragging_entered(&self, _info: DragInfo) -> DragOperation {
/// Cursor::push(CursorType::DragCopy);
/// DragOperation::Copy
/// }
///
/// fn dragging_exited(&self, _info: DragInfo) {
/// Cursor::pop();
/// }
/// }
/// ```
///
/// This will show the "add files +" indicator when the user has entered the dragging threshold
/// with some items that trigger it, and undo the cursor when the user leaves (regardless of drop
/// status).
#[derive(Debug)]
pub struct Cursor;
@ -82,4 +139,19 @@ impl Cursor {
let _: () = msg_send![class!(NSCursor), unhide];
}
}
/// Sets the cursor to hidden, but will reveal it if the user moves the mouse.
///
/// Potentially useful for games and other immersive experiences.
///
/// If you use this, do _not_ use `unhide` - just call this with the inverted boolean value.
/// Trying to invert this with `unhide` will result in undefined system behavior.
pub fn set_hidden_until_mouse_moves(status: bool) {
unsafe {
let _: () = msg_send![class!(NSCursor), setHiddenUntilMouseMoves:match status {
true => YES,
false => NO
}];
}
}
}

View file

@ -1,28 +1,54 @@
//! A lightweight wrapper over some networking components, like `NSURLRequest` and co.
//! This is currently not meant to be exhaustive.
//!
/// At the moment, this is mostly used for inspection of objects returned from system
/// calls, as `NSURL` is pervasive in some filesystem references. Over time this may grow to
/// include a proper networking stack, but the expectation for v0.1 is that most apps will want to
/// use their standard Rust networking libraries (however... odd... the async story may be).
use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::Id;
use objc_id::ShareId;
use crate::foundation::{id, NSString};
/// A wrapper around `NSURLRequest`.
#[derive(Debug)]
pub struct URLRequest {
pub inner: Id<Object>
}
pub struct URLRequest(ShareId<Object>);
impl URLRequest {
pub fn with(inner: id) -> Self {
URLRequest {
inner: unsafe { Id::from_ptr(inner) }
}
/// Wraps and retains an `NSURLRequest`.
pub fn with(request: id) -> Self {
URLRequest(unsafe {
ShareId::from_ptr(request)
})
}
pub fn url(&self) -> String {
/// Returns the underlying request URL as an owned `String`.
pub fn absolute_url(&self) -> String {
NSString::from_retained(unsafe {
let url: id = msg_send![&*self.inner, URL];
let url: id = msg_send![&*self.0, URL];
msg_send![url, absoluteString]
}).to_string()
}
}
#[cfg(test)]
mod tests {
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, NSString};
use crate::networking::URLRequest;
#[test]
fn test_urlrequest() {
let endpoint = "https://rymc.io/";
let url = unsafe {
let url = NSString::new(endpoint);
let url: id = msg_send![class!(NSURL), URLWithString:&*url];
URLRequest::with(msg_send![class!(NSURLRequest), requestWithURL:url])
};
assert_eq!(&url.absolute_url(), endpoint);
}
}

View file

@ -1,7 +1,23 @@
/// A trait for handling dispatched messages on the AppDelegate.
///
/// You can use this for a jank message dispatching mechanism. It has no guarantees concerning
/// performance, but is good enough for many applications. Implement this trait on your struct
/// that implements `AppDelegate`, and then dispatch messages like the following:
///
/// ```rust,no_run
/// App::<YourAppDelegate, YourMessageType>::dispatch_main(your_message);
/// ```
///
/// This will asynchronously loop a message back to the "top" of your app, via your app delegate.
/// You can process it from there.
pub trait Dispatcher {
/// The type of Message you're sending. This should be lightweight and thread safe.
type Message: Send + Sync;
/// Called when a message is looped back on the _main_ queue. This is where all UI work should
/// be happening.
fn on_ui_message(&self, _message: Self::Message) {}
/// Called when a message is looped back on a background queue.
fn on_background_message(&self, _message: Self::Message) {}
}

View file

@ -1,21 +0,0 @@
use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl};
/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we
/// have separate classes here since we don't want to waste cycles on methods that will never be
/// used if there's no delegates.
pub(crate) fn register_progress_indicator_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!(NSProgressIndicator);
let decl = ClassDecl::new("RSTProgressIndicator", superclass).unwrap();
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
}

View file

@ -1,53 +1,63 @@
//! A progress indicator widget.
//!
//! This control wraps `NSProgressIndicator` on macOS, and
//! `UIProgressView+UIActivityIndicatorView` on iOS and tvOS. It operates in two modes: determinate
//! (where you have a fixed start and end) and indeterminate (infinite; it will go and go until you
//! tell it to stop).
//!
//! ```rust,no_run
//! let indicator = ProgressIndicator::new();
//! indicator.set_indeterminate(true);
//! my_view.add_subview(&indicator);
//! ```
use core_graphics::base::CGFloat;
use objc_id::ShareId;
use objc::runtime::{Class, Object};
use objc::{msg_send, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSUInteger};
use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use macos::{register_progress_indicator_class};
#[cfg(target_os = "ios")]
mod ios;
#[cfg(target_os = "ios")]
use ios::{register_progress_indicator_class};
use ios::register_progress_indicator_class;
mod enums;
pub use enums::ProgressIndicatorStyle;
/// A control used for reporting progress to a user visually.
#[derive(Debug)]
pub struct ProgressIndicator {
/// A pointer to the Objective-C runtime view controller.
/// A pointer to the Objective-C Object.
pub objc: ShareId<Object>,
/// A pointer to the Objective-C runtime top layout constraint.
/// A pointer to the Objective-C top layout constraint.
pub top: LayoutAnchorY,
/// A pointer to the Objective-C runtime leading layout constraint.
/// A pointer to the Objective-C leading layout constraint.
pub leading: LayoutAnchorX,
/// A pointer to the Objective-C runtime trailing layout constraint.
/// A pointer to the Objective-C trailing layout constraint.
pub trailing: LayoutAnchorX,
/// A pointer to the Objective-C runtime bottom layout constraint.
/// A pointer to the Objective-C bottom layout constraint.
pub bottom: LayoutAnchorY,
/// A pointer to the Objective-C runtime width layout constraint.
/// A pointer to the Objective-C width layout constraint.
pub width: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime height layout constraint.
/// A pointer to the Objective-C height layout constraint.
pub height: LayoutAnchorDimension,
/// A pointer to the Objective-C runtime center X layout constraint.
/// A pointer to the Objective-C center X layout constraint.
pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint.
/// A pointer to the Objective-C center Y layout constraint.
pub center_y: LayoutAnchorY
}
@ -58,13 +68,15 @@ impl Default for ProgressIndicator {
}
impl ProgressIndicator {
/// Returns a default `ProgressIndicator`, suitable for
/// Returns a default `ProgressIndicator`. You should retain this yourself for as long as you
/// need it to stay around.
pub fn new() -> Self {
let view = unsafe {
let view: id = msg_send![register_progress_indicator_class(), new];
#[cfg(feature = "macos")]
let view: id = msg_send![class!(NSProgressIndicator), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
#[cfg(target_os = "macos")]
#[cfg(feature = "macos")]
let _: () = msg_send![view, setWantsLayer:YES];
view
@ -85,17 +97,6 @@ impl ProgressIndicator {
}
impl ProgressIndicator {
// Call this to set the background color for the backing layer.
/*pub fn set_background_color(&self, color: Color) {
let bg = color.into_platform_specific_color();
unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg];
}
}*/
/// Starts the animation for an indeterminate indicator.
pub fn start_animation(&self) {
unsafe {
@ -103,18 +104,22 @@ impl ProgressIndicator {
}
}
/// Stops any animations that are currently happening on this indicator (e.g, if it's an
/// indeterminate looping animation).
pub fn stop_animation(&self) {
unsafe {
let _: () = msg_send![&*self.objc, stopAnimation:nil];
}
}
pub fn increment(&self, by: f64) {
/// Increment the progress indicator by the amount specified.
pub fn increment(&self, amount: f64) {
unsafe {
let _: () = msg_send![&*self.objc, incrementBy:by];
let _: () = msg_send![&*self.objc, incrementBy:amount];
}
}
/// Set the style for the progress indicator.
pub fn set_style(&self, style: ProgressIndicatorStyle) {
unsafe {
let style = style as NSUInteger;
@ -122,6 +127,10 @@ impl ProgressIndicator {
}
}
/// Set whether this is an indeterminate indicator or not. Indeterminate indicators are
/// "infinite" and their appearance is that of a circular spinner.
///
/// Invert this to go back to a bar appearance.
pub fn set_indeterminate(&self, is_indeterminate: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setIndeterminate:match is_indeterminate {
@ -130,6 +139,27 @@ impl ProgressIndicator {
}];
}
}
/// Sets the value of this progress indicator.
///
/// If this progress indicator is indeterminate, this will have no effect.
pub fn set_value(&self, value: f64) {
let value = value as CGFloat;
unsafe {
let _: () = msg_send![&*self.objc, setDoubleValue:value];
}
}
/// Set whether this control is hidden or not.
pub fn set_hidden(&self, hidden: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden {
true => YES,
false => NO
}];
}
}
}
impl Layout for ProgressIndicator {
@ -147,20 +177,19 @@ impl Layout for ProgressIndicator {
}
impl Drop for ProgressIndicator {
/// A bit of extra cleanup for delegate callback pointers. If the originating `ProgressIndicator` is being
/// A bit of extra cleanup for delegate callback pointers.
/// If the originating `ProgressIndicator` is being
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
/// this has a superview (i.e, it's in the heirarchy) on the AppKit side. If it does, we go
/// this has a superview (i.e, it's in the heirarchy) on the Objective-C side. If it does, we go
/// ahead and remove it - this is intended to match the semantics of how Rust handles things).
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
/*if self.delegate.is_some() {
unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}*/
}
}

View file

@ -1,6 +1,9 @@
use crate::dragdrop::{DragInfo, DragOperation};
use crate::scrollview::ScrollView;
/// A ScrollViewDelegate implements methods that you might need or want to respond to. In addition
/// to scroll-specific events, it enables implementing certain standard `View` handlers for things
/// like drag and drop.
pub trait ScrollViewDelegate {
/// Called when the View is ready to work with. You're passed a `View` - this is safe to
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the

View file

@ -18,6 +18,7 @@ use crate::utils::load;
/// where our `NSSwitch` lives.
#[derive(Debug)]
pub struct Switch {
/// A pointer to the underlying Objective-C Object.
pub objc: ShareId<Object>,
handler: Option<TargetActionHandler>,

View file

@ -223,7 +223,8 @@ impl<T> Label<T> {
}
/// Call this to set the text for the label.
pub fn set_text(&self, text: &str) {
pub fn set_text<S: AsRef<str>>(&self, text: S) {
let text = text.as_ref();
let s = NSString::new(text);
unsafe {
@ -269,12 +270,20 @@ impl<T> Label<T> {
}
}
/// Sets the maximum number of lines.
pub fn set_max_number_of_lines(&self, num: NSInteger) {
unsafe {
let _: () = msg_send![&*self.objc, setMaximumNumberOfLines:num];
}
}
/// Set the line break mode for this label.
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
#[cfg(target_os = "macos")]
unsafe {
let cell: id = msg_send![&*self.objc, cell];
let mode = mode as NSUInteger;
let _: () = msg_send![cell, setTruncatesLastVisibleLine:YES];
let _: () = msg_send![cell, setLineBreakMode:mode];
}
}

71
src/utils/cell_factory.rs Normal file
View file

@ -0,0 +1,71 @@
use std::any::Any;
use std::fmt;
use std::collections::HashMap;
use std::cell::RefCell;
use std::rc::Rc;
use crate::view::ViewDelegate;
type CellFactoryMap = HashMap<&'static str, Box<dyn Fn() -> Box<dyn Any>>>;
/// A CellFactory is an struct that stores closures that instantiate view types.
///
/// This is a pattern used in certain view types (e.g, `ListView`). This factory exists to enable
/// dynamic view registration and dequeueing. It stores a closure and erases the type to `Any`, and
/// supports querying for that time to get it back.
///
/// It is explicitly designed to panic if it's unable to retrieve a stored item with the specified
/// type, as the views that use this would cease to function if the type can't be retrieved, and
/// it's better to blow up early.
#[derive(Clone)]
pub struct CellFactory(pub Rc<RefCell<CellFactoryMap>>);
impl CellFactory {
/// Creates and returns a new CellFactory.
pub fn new() -> Self {
CellFactory(Rc::new(RefCell::new(HashMap::new())))
}
/// Store a closure for the given identifier.
// @TODO: We might not need to do anything with this being `ViewDelegate`...
pub fn insert<F, T>(&self, identifier: &'static str, vendor: F)
where
F: Fn() -> T + 'static,
T: ViewDelegate + 'static
{
let mut lock = self.0.borrow_mut();
lock.insert(identifier, Box::new(move || {
let cell = vendor();
Box::new(cell) as Box<dyn Any>
}));
}
/// Attempts to retrieve the closure, downcasted to the specified type. This will panic if it's
/// unable to retrieve the closure with the requested type.
pub fn get<R>(&self, identifier: &'static str) -> Box<R>
where
R: ViewDelegate + 'static
{
let lock = self.0.borrow();
let vendor = match lock.get(identifier) {
Some(v) => v,
None => {
panic!("Unable to dequeue cell for {}: did you forget to register it?", identifier);
}
};
let view = vendor();
if let Ok(view) = view.downcast::<R>() {
view
} else {
panic!("Asking for cell of type {}, but failed to match the type!", identifier);
}
}
}
impl std::fmt::Debug for CellFactory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CellFactory").finish()
}
}

View file

@ -12,6 +12,9 @@ use objc_id::ShareId;
use crate::foundation::{id, BOOL, YES, NO};
mod cell_factory;
pub use cell_factory::CellFactory;
pub mod os;
/// A generic trait that's used throughout multiple different controls in this framework - acts as

View file

@ -3,14 +3,22 @@ use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, NSString};
use crate::layout::{Layout};
use crate::layout::Layout;
use crate::macos::toolbar::ToolbarItem;
use crate::view::{View, ViewController, ViewDelegate};
use crate::utils::Controller;
use crate::utils::{os, Controller};
/// A SplitViewItem wraps a ViewController, and provides system hooks for operating in a
/// SplitView(Controller).
///
/// This is typically created for you when you create a `SplitViewController`, but is exported in
/// case you need to hook into the underlying platform pieces.
#[derive(Debug)]
pub struct SplitViewItem<T> {
/// The underlying Objective-C Object.
pub objc: ShareId<Object>,
/// The wrapped ViewController.
pub view_controller: ViewController<T>
}
@ -18,65 +26,128 @@ impl<T> SplitViewItem<T>
where
T: ViewDelegate + 'static
{
/// Creates and returns a new `SplitViewItem`. This has no special properties out the default
/// split view item type.
pub fn item(view: T) -> Self {
let view_controller = ViewController::new(view);
let objc = unsafe {
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), splitViewItemWithViewController:&*view_controller.objc])
};
SplitViewItem {
objc,
objc: unsafe {
ShareId::from_ptr(msg_send![class!(NSSplitViewItem),
splitViewItemWithViewController:&*view_controller.objc
])
},
view_controller
}
}
/// Creates and returns a new `SplitViewItem`. The returned item is optimized to be a
/// "sidebar"; that is, a typically left-most view that should be treated as such.
///
/// On macOS Big Sur, this automatically gets the vibrancy backed sidebar view and will extend
/// extend to the top of the window provided the other necessary window flags are set. On macOS
/// versions prior to Big Sur, this returns a standard SplitViewItem.
pub fn sidebar(view: T) -> Self {
if !os::is_minimum_version(11) {
return Self::item(view);
}
let view_controller = ViewController::new(view);
let objc = unsafe {
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), sidebarWithViewController:&*view_controller.objc])
};
SplitViewItem {
objc,
objc: unsafe {
ShareId::from_ptr(msg_send![class!(NSSplitViewItem),
sidebarWithViewController:&*view_controller.objc
])
},
view_controller
}
}
/// Sets the titlebar separator style for this `SplitView`.
///
/// You'd use this if, say, you wanted a border under one part of the `SplitViewController` but
/// not the other. This API was introduced in macOS 11.0 (Big Sur) and is a noop on anything
/// prior.
#[cfg(feature = "macos")]
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
if os::is_minimum_version(11) {
unsafe {
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
}
}
}
}
/// A SplitViewController manages two or more view controllers in a split-pane view.
///
/// You typically use this controller as a content view controller for a `Window`. With it, you can
/// build interfaces like those found in Mail.app or Xcode. Dividers can be configured to save
/// their positions so that users can adjust them as they please.
///
/// Note that the third pane is optional; you can opt to leave it `None`, in which case there's no
/// allocation there, or you can set a placeholder and use it as a details pane.
///
/// A note on property names: the Cocoa(Touch) controllers tend to view these as:
///
/// `|sidebar|details|content|`
///
/// This pattern fits things such as a the aforementioned apps (e.g, Mail). Cacao takes the
/// position that most apps really end up doing the following, though:
///
/// `|sidebar|content|details|`
///
/// where details may or may not be visible (e.g, chat applications often work this way).
#[derive(Debug)]
pub struct SplitViewController<Sidebar, Content> {
pub struct SplitViewController<Sidebar, Content, Details> {
/// A reference to the underlying Objective-C split view controller.
pub objc: ShareId<Object>,
/// A reference to the sidebar `SplitViewItem`.
pub sidebar: SplitViewItem<Sidebar>,
/// A reference to the content `SplitViewItem`.
pub content: SplitViewItem<Content>,
/// An optional reference to the details `SplitViewItem`, if set.
pub details: Option<SplitViewItem<Details>>
}
impl<Sidebar, Content> SplitViewController<Sidebar, Content>
impl<Sidebar, Content, Details> SplitViewController<Sidebar, Content, Details>
where
Sidebar: ViewDelegate + 'static,
Content: ViewDelegate + 'static
Content: ViewDelegate + 'static,
Details: ViewDelegate + 'static
{
pub fn new(sidebar: Sidebar, content: Content) -> Self {
/// Creates and returns a new `SplitViewController`.
pub fn new(sidebar: Sidebar, content: Content, details: Option<Details>) -> Self {
let sidebar = SplitViewItem::sidebar(sidebar);
let content = SplitViewItem::item(content);
let details = match details {
Some(vc) => Some(SplitViewItem::item(vc)),
None => None
};
let objc = unsafe {
let vc: id = msg_send![class!(NSSplitViewController), new];
let _: () = msg_send![vc, addSplitViewItem:&*sidebar.objc];
let _: () = msg_send![vc, addSplitViewItem:&*content.objc];
if let Some(details) = &details {
let _: () = msg_send![vc, addSplitViewItem:&*details.objc];
}
ShareId::from_ptr(vc)
};
SplitViewController { objc, sidebar, content }
SplitViewController { objc, sidebar, content, details }
}
}
impl<Sidebar, Content, Details> SplitViewController<Sidebar, Content, Details> {
/// Toggles the sidebar, if it exists, with an animation. If there's no sidebar in this split view
/// (which is highly unlikely, unless you went out of your way to duck this) then it will do
/// nothing.
@ -86,6 +157,10 @@ where
}
}
/// Sets the autosave name for the underlying `SplitView`.
///
/// Setting this name causes the system to persist separator locations to a defaults database,
/// and the position(s) will be restored upon the user reopening the application.
pub fn set_autosave_name(&self, name: &str) {
let name = NSString::new(name);
@ -94,29 +169,9 @@ where
let _: () = msg_send![split_view, setAutosaveName:&*name];
}
}
/*/// This method can be used to acquire an item for Toolbar instances that tracks a specified
/// divider (`divider_index`) of this split view. This method is only supported on macOS 11.0+;
/// it will return `None` on 10.15 and below.
///
/// You should call this and pass + store the item in your Toolbar, and vend it to the system
/// with your `ToolbarDelegate`.
pub fn tracking_separator_toolbar_item(&self, divider_index: usize) -> Option<ToolbarItem> {
if crate::utils::os::is_minimum_version(11) {
unsafe {
let split_view: id = msg_send![&*self.objc, splitView];
let item: id = msg_send![class!(NSTrackingSeparatorToolbarItem), trackingSeparatorToolbarItemWithIdentifier:
splitView:split_view
dividerIndex:divider_index as NSInteger
];
}
}
None
}*/
}
impl<Sidebar, Content> Controller for SplitViewController<Sidebar, Content> {
impl<Sidebar, Content, Details> Controller for SplitViewController<Sidebar, Content, Details> {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}

View file

@ -56,6 +56,8 @@ impl From<id> for OpenPanelParameters {
match msg_send![params, allowsDirectories] {
YES => true,
NO => false,
#[cfg(not(target_arch = "aarch64"))]
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsDirectories"); }
}
},
@ -64,6 +66,8 @@ impl From<id> for OpenPanelParameters {
match msg_send![params, allowsMultipleSelection] {
YES => true,
NO => false,
#[cfg(not(target_arch = "aarch64"))]
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsMultipleSelection"); }
}
}

View file

@ -47,8 +47,8 @@ extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_me
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
unsafe {
let name = NSString::wrap(msg_send![script_message, name]);
let body = NSString::wrap(msg_send![script_message, body]);
let name = NSString::from_retained(msg_send![script_message, name]);
let body = NSString::from_retained(msg_send![script_message, body]);
delegate.on_message(name.to_str(), body.to_str());
}
}
@ -88,10 +88,10 @@ extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, param
Some(u) => {
let nsurls: NSArray = u.iter().map(|s| {
let s = NSString::new(s);
msg_send![class!(NSURL), URLWithString:s.into_inner()]
msg_send![class!(NSURL), URLWithString:&*s]
}).collect::<Vec<id>>().into();
(*handler).call((nsurls.into_inner(),));
(*handler).call((nsurls.into(),));
},
None => { (*handler).call((nil,)); }
@ -102,12 +102,12 @@ extern fn run_open_panel<T: WebViewDelegate>(this: &Object, _: Sel, _: id, param
/// Called when a download has been initiated in the WebView, and when the navigation policy
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
/// API.
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: id, suggested_filename: id, handler: usize) {
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
let filename = NSString::wrap(suggested_filename);
let filename = NSString::from_retained(suggested_filename);
delegate.run_save_panel(filename.to_str(), move |can_overwrite, path| unsafe {
if path.is_none() {
@ -119,7 +119,7 @@ extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: i
(*handler).call((match can_overwrite {
true => YES,
false => NO
}, path.into_inner()));
}, path.into()));
});
}
@ -166,7 +166,7 @@ pub fn register_webview_delegate_class<T: WebViewDelegate>() -> *const Class {
// WKDownloadDelegate is a private class on macOS that handles downloading (saving) files.
// It's absurd that this is still private in 2020. This probably couldn't get into the app
// store, so... screw it, feature-gate it.
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));
VIEW_CLASS = decl.register();

View file

@ -10,6 +10,7 @@ use crate::webview::enums::InjectAt;
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime
/// where everything lives.
#[derive(Debug)]
pub struct WebViewConfig {
pub objc: Id<Object>,
pub handlers: Vec<String>

View file

@ -85,7 +85,7 @@ pub enum NavigationResponsePolicy {
/// This is a private API, and likely won't make it into the App Store. Will only be available
/// if you opt in via the `webview-downloading` feature.
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
BecomeDownload
}
@ -95,7 +95,7 @@ impl From<NavigationResponsePolicy> for NSInteger {
NavigationResponsePolicy::Cancel => 0,
NavigationResponsePolicy::Allow => 1,
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
NavigationResponsePolicy::BecomeDownload => 2
}
}

View file

@ -52,9 +52,9 @@ fn allocate_webview(
if let Some(delegate) = &objc_delegate {
// Technically private!
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
let process_pool: id = msg_send![configuration, processPool];
#[cfg(feature = "webview-downloading")]
#[cfg(feature = "webview-downloading-macos")]
let _: () = msg_send![process_pool, _setDownloadDelegate:*delegate];
let content_controller: id = msg_send![configuration, userContentController];
@ -203,7 +203,7 @@ impl<T> WebView<T> {
let url = NSString::new(url);
unsafe {
let u: id = msg_send![class!(NSURL), URLWithString:url.into_inner()];
let u: id = msg_send![class!(NSURL), URLWithString:&*url];
let request: id = msg_send![class!(NSURLRequest), requestWithURL:u];
let _: () = msg_send![&*self.objc, loadRequest:request];
}