Ongoing documentation and cleanup work.
This commit is contained in:
parent
bc54b49475
commit
696907aa73
|
@ -24,8 +24,12 @@ use crate::macos::FocusRingType;
|
||||||
/// where our `NSButton` lives.
|
/// where our `NSButton` lives.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
|
/// A handle for the underlying Objective-C object.
|
||||||
pub objc: ShareId<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>,
|
pub image: Option<Image>,
|
||||||
|
|
||||||
handler: Option<TargetActionHandler>,
|
handler: Option<TargetActionHandler>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime top layout constraint.
|
/// A pointer to the Objective-C runtime top layout constraint.
|
||||||
|
@ -60,7 +64,11 @@ impl Button {
|
||||||
let title = NSString::new(text);
|
let title = NSString::new(text);
|
||||||
|
|
||||||
let view: id = unsafe {
|
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, setWantsLayer:YES];
|
||||||
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
let _: () = msg_send![button, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
button
|
button
|
||||||
|
@ -81,6 +89,7 @@ impl Button {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets an image on the underlying button.
|
||||||
pub fn set_image(&mut self, image: Image) {
|
pub fn set_image(&mut self, image: Image) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setImage:&*image.0];
|
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) {
|
pub fn set_key_equivalent(&self, key: &str) {
|
||||||
let key = NSString::new(key);
|
let key = NSString::new(key);
|
||||||
|
|
||||||
|
@ -241,19 +252,47 @@ fn register_class() -> *const Class {
|
||||||
#[cfg(feature = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum BezelStyle {
|
pub enum BezelStyle {
|
||||||
|
/// A standard circular button.
|
||||||
Circular,
|
Circular,
|
||||||
|
|
||||||
|
/// A standard disclosure style button.
|
||||||
Disclosure,
|
Disclosure,
|
||||||
|
|
||||||
|
/// The standard looking "Help" (?) button.
|
||||||
HelpButton,
|
HelpButton,
|
||||||
|
|
||||||
|
/// An inline style, varies across OS's.
|
||||||
Inline,
|
Inline,
|
||||||
|
|
||||||
|
/// A recessed style, varies slightly across OS's.
|
||||||
Recessed,
|
Recessed,
|
||||||
|
|
||||||
|
/// A regular square style, with no special styling.
|
||||||
RegularSquare,
|
RegularSquare,
|
||||||
|
|
||||||
|
/// A standard rounded rectangle.
|
||||||
RoundRect,
|
RoundRect,
|
||||||
|
|
||||||
|
/// A standard rounded button.
|
||||||
Rounded,
|
Rounded,
|
||||||
|
|
||||||
|
/// A standard rounded disclosure button.
|
||||||
RoundedDisclosure,
|
RoundedDisclosure,
|
||||||
|
|
||||||
|
/// A shadowless square styl.e
|
||||||
ShadowlessSquare,
|
ShadowlessSquare,
|
||||||
|
|
||||||
|
/// A small square style.
|
||||||
SmallSquare,
|
SmallSquare,
|
||||||
|
|
||||||
|
/// A textured rounded style.
|
||||||
TexturedRounded,
|
TexturedRounded,
|
||||||
|
|
||||||
|
/// A textured square style.
|
||||||
TexturedSquare,
|
TexturedSquare,
|
||||||
|
|
||||||
|
/// Any style that's not known by this framework (e.g, if Apple
|
||||||
|
/// introduces something new).
|
||||||
Unknown(NSUInteger)
|
Unknown(NSUInteger)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,22 @@
|
||||||
|
|
||||||
use crate::foundation::NSUInteger;
|
use crate::foundation::NSUInteger;
|
||||||
|
|
||||||
|
/// Flags that indicate a key is in the mix for an event.
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum EventModifierFlag {
|
pub enum EventModifierFlag {
|
||||||
|
/// CapsLock (or shift... oddly named...) is held.
|
||||||
CapsLock,
|
CapsLock,
|
||||||
|
|
||||||
|
/// Control is held.
|
||||||
Control,
|
Control,
|
||||||
|
|
||||||
|
/// Option is held.
|
||||||
Option,
|
Option,
|
||||||
|
|
||||||
|
/// Command (CMD) is held.
|
||||||
Command,
|
Command,
|
||||||
|
|
||||||
|
/// Device independent flags mask.
|
||||||
DeviceIndependentFlagsMask
|
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)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum EventType {
|
pub enum EventType {
|
||||||
|
/// A keydown event.
|
||||||
KeyDown
|
KeyDown
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,31 @@
|
||||||
|
|
||||||
use crate::foundation::{NSInteger, NSUInteger};
|
use crate::foundation::{NSInteger, NSUInteger};
|
||||||
|
|
||||||
|
/// Represents a modal response for macOS modal dialogs.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum ModalResponse {
|
pub enum ModalResponse {
|
||||||
|
/// The user hit the "Ok" button.
|
||||||
Ok,
|
Ok,
|
||||||
|
|
||||||
|
/// Continue.
|
||||||
Continue,
|
Continue,
|
||||||
|
|
||||||
|
/// Canceled.
|
||||||
Canceled,
|
Canceled,
|
||||||
|
|
||||||
|
/// Stopped.
|
||||||
Stopped,
|
Stopped,
|
||||||
|
|
||||||
|
/// Aborted.
|
||||||
Aborted,
|
Aborted,
|
||||||
|
|
||||||
|
/// The first button in the dialog was clicked.
|
||||||
FirstButtonReturned,
|
FirstButtonReturned,
|
||||||
|
|
||||||
|
/// The second button in the dialog was clicked.
|
||||||
SecondButtonReturned,
|
SecondButtonReturned,
|
||||||
|
|
||||||
|
/// The third button in the dialog was clicked.
|
||||||
ThirdButtonReturned
|
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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum SearchPathDomainMask {
|
pub enum SearchPathDomainMask {
|
||||||
|
/// User files and folders.
|
||||||
User,
|
User,
|
||||||
|
|
||||||
|
/// Local volume files and folders.
|
||||||
Local,
|
Local,
|
||||||
|
|
||||||
|
/// Netowrk files and folders.
|
||||||
Network,
|
Network,
|
||||||
|
|
||||||
|
/// Search all domains. Not typically used these days.
|
||||||
Domain,
|
Domain,
|
||||||
|
|
||||||
|
/// Search all domains. Not typically used these days.
|
||||||
AllDomains
|
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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum SearchPathDirectory {
|
pub enum SearchPathDirectory {
|
||||||
|
/// The applications folder.
|
||||||
Applications,
|
Applications,
|
||||||
|
|
||||||
|
/// Unsupported applications and demo versions. Not generally used these days.
|
||||||
DemoApplications,
|
DemoApplications,
|
||||||
|
|
||||||
|
/// Developer applications (_/Developer/Applications_). Not generally used these days.
|
||||||
DeveloperApplications,
|
DeveloperApplications,
|
||||||
|
|
||||||
|
/// System and network admin apps.
|
||||||
AdminApplications,
|
AdminApplications,
|
||||||
|
|
||||||
|
/// User-visible docs, support, and config files.
|
||||||
Library,
|
Library,
|
||||||
|
|
||||||
|
/// Dev resources. (_/Developer_)
|
||||||
Developer,
|
Developer,
|
||||||
|
|
||||||
|
/// User home directories. (_/Users_)
|
||||||
User,
|
User,
|
||||||
|
|
||||||
|
/// Documentation.
|
||||||
Documentation,
|
Documentation,
|
||||||
|
|
||||||
|
/// Documents directory.
|
||||||
Documents,
|
Documents,
|
||||||
|
|
||||||
|
/// Core Services (_/System/Library/CoreServices_)
|
||||||
CoreServices,
|
CoreServices,
|
||||||
|
|
||||||
|
/// User's autosaved documents (_/Library/Autosave Information_)
|
||||||
AutosavedInformation,
|
AutosavedInformation,
|
||||||
|
|
||||||
|
/// The current user's Desktop directory.
|
||||||
Desktop,
|
Desktop,
|
||||||
|
|
||||||
|
/// Discardable cache files. (_/Library/Caches_)
|
||||||
Caches,
|
Caches,
|
||||||
|
|
||||||
|
/// App support files (_/Library/Application Support_)
|
||||||
ApplicationSupport,
|
ApplicationSupport,
|
||||||
|
|
||||||
|
/// The curent user's Downloads directory.
|
||||||
Downloads,
|
Downloads,
|
||||||
|
|
||||||
|
/// Input methods (_/Library/Input Methods_)
|
||||||
InputMethods,
|
InputMethods,
|
||||||
|
|
||||||
|
/// The current user's Movies directory.
|
||||||
Movies,
|
Movies,
|
||||||
|
|
||||||
|
/// The current user's Music directory.
|
||||||
Music,
|
Music,
|
||||||
|
|
||||||
|
/// The current user's pictures directory.
|
||||||
Pictures,
|
Pictures,
|
||||||
|
|
||||||
|
/// System PPD files (_/Library/Printers/PPDs_)
|
||||||
PrinterDescription,
|
PrinterDescription,
|
||||||
|
|
||||||
|
/// The current user's public sharing directory.
|
||||||
SharedPublic,
|
SharedPublic,
|
||||||
|
|
||||||
|
/// The Preferences Pane directory, where system preferences files live.
|
||||||
|
/// (_/Library/PreferencePanes_)
|
||||||
PreferencePanes,
|
PreferencePanes,
|
||||||
|
|
||||||
|
/// The user scripts folder for the calling application
|
||||||
|
/// (_~/Library/Application Scripts/<code-signing-id>_).
|
||||||
ApplicationScripts,
|
ApplicationScripts,
|
||||||
|
|
||||||
|
/// Constant used in creating a temp directory.
|
||||||
ItemReplacement,
|
ItemReplacement,
|
||||||
|
|
||||||
|
/// All directories where apps can be stored.
|
||||||
AllApplications,
|
AllApplications,
|
||||||
|
|
||||||
|
/// All directories where resources can be stored.
|
||||||
AllLibraries,
|
AllLibraries,
|
||||||
|
|
||||||
|
/// The Trash directory.
|
||||||
Trash
|
Trash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +199,6 @@ impl From<SearchPathDirectory> for NSUInteger {
|
||||||
SearchPathDirectory::SharedPublic => 21,
|
SearchPathDirectory::SharedPublic => 21,
|
||||||
SearchPathDirectory::PreferencePanes => 22,
|
SearchPathDirectory::PreferencePanes => 22,
|
||||||
SearchPathDirectory::ApplicationScripts => 23,
|
SearchPathDirectory::ApplicationScripts => 23,
|
||||||
|
|
||||||
SearchPathDirectory::ItemReplacement => 99,
|
SearchPathDirectory::ItemReplacement => 99,
|
||||||
SearchPathDirectory::AllApplications => 100,
|
SearchPathDirectory::AllApplications => 100,
|
||||||
SearchPathDirectory::AllLibraries => 101,
|
SearchPathDirectory::AllLibraries => 101,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! tricky, and this transparently handles it for you).
|
//! tricky, and this transparently handles it for you).
|
||||||
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::sync::RwLock;
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
use objc::runtime::{BOOL, Object};
|
use objc::runtime::{BOOL, Object};
|
||||||
|
@ -10,47 +10,51 @@ use objc::{class, msg_send, sel, sel_impl};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NO, NSString, NSUInteger};
|
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};
|
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// A FileManager can be used for file operations (moving files, etc).
|
||||||
pub struct FileManager {
|
///
|
||||||
pub manager: RwLock<Id<Object>>
|
/// 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 {
|
impl Default for FileManager {
|
||||||
/// Returns a default file manager, which maps to the default system file manager. For common
|
/// 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.
|
/// and simple tasks, with no callbacks, you might want this.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FileManager {
|
FileManager(Arc::new(RwLock::new(unsafe {
|
||||||
manager: RwLock::new(unsafe {
|
let manager: id = msg_send![class!(NSFileManager), defaultManager];
|
||||||
let manager: id = msg_send![class!(NSFileManager), defaultManager];
|
Id::from_ptr(manager)
|
||||||
Id::from_ptr(manager)
|
})))
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileManager {
|
impl FileManager {
|
||||||
/// Returns a new FileManager that opts in to delegate methods.
|
/// Returns a new FileManager that opts in to delegate methods.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
FileManager {
|
FileManager(Arc::new(RwLock::new(unsafe {
|
||||||
manager: RwLock::new(unsafe {
|
let manager: id = msg_send![class!(NSFileManager), new];
|
||||||
let manager: id = msg_send![class!(NSFileManager), new];
|
Id::from_ptr(manager)
|
||||||
Id::from_ptr(manager)
|
})))
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a directory/domain combination, will attempt to get the directory that matches.
|
/// 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
|
/// 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.
|
/// 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 dir: NSUInteger = directory.into();
|
||||||
let mask: NSUInteger = in_domain.into();
|
let mask: NSUInteger = in_domain.into();
|
||||||
|
|
||||||
let directory = unsafe {
|
let directory = unsafe {
|
||||||
let manager = self.manager.read().unwrap();
|
let manager = self.0.read().unwrap();
|
||||||
let dir: id = msg_send![&**manager, URLForDirectory:dir
|
let dir: id = msg_send![&**manager, URLForDirectory:dir
|
||||||
inDomain:mask
|
inDomain:mask
|
||||||
appropriateForURL:nil
|
appropriateForURL:nil
|
||||||
|
@ -76,7 +80,7 @@ impl FileManager {
|
||||||
|
|
||||||
// This should potentially be write(), but the backing class handles this logic
|
// This should potentially be write(), but the backing class handles this logic
|
||||||
// already, so... going to leave it as read.
|
// 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 error: id = nil;
|
||||||
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
let result: BOOL = msg_send![&**manager, moveItemAtURL:from_url toURL:to_url error:&error];
|
||||||
|
|
|
@ -47,12 +47,23 @@ impl FileSavePanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @TODO: Do we even need this?
|
||||||
pub fn set_delegate(&mut self) {}
|
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 {
|
unsafe {
|
||||||
let filename = NSString::new(suggested_filename);
|
let filename = NSString::new(suggested_filename.as_ref());
|
||||||
let _: () = msg_send![&*self.panel, setNameFieldStringValue:filename];
|
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,14 @@ impl FileSelectPanel {
|
||||||
self.can_choose_files = can_choose;
|
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.
|
/// Sets whether the user can choose directories.
|
||||||
pub fn set_can_choose_directories(&mut self, can_choose: bool) {
|
pub fn set_can_choose_directories(&mut self, can_choose: bool) {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -17,6 +17,7 @@ const UTF8_ENCODING: usize = 4;
|
||||||
/// side is fairly battle tested.
|
/// side is fairly battle tested.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NSString<'a> {
|
pub struct NSString<'a> {
|
||||||
|
/// A reference to the backing `NSString`.
|
||||||
pub objc: Id<Object>,
|
pub objc: Id<Object>,
|
||||||
phantom: PhantomData<&'a ()>
|
phantom: PhantomData<&'a ()>
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,19 @@ use crate::foundation::{id, YES, NO, NSString};
|
||||||
use crate::utils::os;
|
use crate::utils::os;
|
||||||
use super::icons::*;
|
use super::icons::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
/// Specifies resizing behavior for image drawing.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum ResizeBehavior {
|
pub enum ResizeBehavior {
|
||||||
|
/// Fit to the aspect ratio.
|
||||||
AspectFit,
|
AspectFit,
|
||||||
|
|
||||||
|
/// Fill the aspect ratio.
|
||||||
AspectFill,
|
AspectFill,
|
||||||
|
|
||||||
|
/// Stretch as necessary.
|
||||||
Stretch,
|
Stretch,
|
||||||
|
|
||||||
|
/// Center and then let whatever else flow around it.
|
||||||
Center
|
Center
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +50,8 @@ fn min_cgfloat(x: CGFloat, y: CGFloat) -> CGFloat {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizeBehavior {
|
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 {
|
pub fn apply(&self, source: CGRect, target: CGRect) -> CGRect {
|
||||||
// if equal, just return source
|
// if equal, just return source
|
||||||
if
|
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 {
|
pub struct DrawConfig {
|
||||||
|
/// The size of the source.
|
||||||
pub source: (CGFloat, CGFloat),
|
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),
|
pub target: (CGFloat, CGFloat),
|
||||||
|
|
||||||
|
/// The type of resizing to use during drawing and scaling.
|
||||||
pub resize: ResizeBehavior
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image(pub ShareId<Object>);
|
pub struct Image(pub ShareId<Object>);
|
||||||
|
|
||||||
|
@ -195,7 +214,11 @@ impl Image {
|
||||||
let block = block.copy();
|
let block = block.copy();
|
||||||
|
|
||||||
Image(unsafe {
|
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)
|
ShareId::from_ptr(img)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,7 @@ impl<T> TextField<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The the text alignment style for this control.
|
||||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let alignment: NSInteger = alignment.into();
|
let alignment: NSInteger = alignment.into();
|
||||||
|
@ -232,7 +233,7 @@ impl<T> TextField<T> {
|
||||||
|
|
||||||
/// Sets the font for this input.
|
/// Sets the font for this input.
|
||||||
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
||||||
let font = font.as_ref();
|
let font = font.as_ref().clone();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setFont:&*font];
|
let _: () = msg_send![&*self.objc, setFont:&*font];
|
||||||
|
|
|
@ -208,7 +208,12 @@ impl From<NSUInteger> for LayoutFormat {
|
||||||
/// Specifies layout priority.
|
/// Specifies layout priority.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LayoutPriority {
|
pub enum LayoutPriority {
|
||||||
|
/// Highest priority.
|
||||||
Required,
|
Required,
|
||||||
|
|
||||||
|
/// High priority. Will bend if absolutely necessary.
|
||||||
High,
|
High,
|
||||||
|
|
||||||
|
/// Low priority.
|
||||||
Low
|
Low
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum RowEdge {
|
pub enum RowEdge {
|
||||||
|
/// The leading edge.
|
||||||
Leading,
|
Leading,
|
||||||
|
|
||||||
|
/// The trailing edge.
|
||||||
Trailing
|
Trailing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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::foundation::{load_or_register_class, id, nil, YES, NO, NSArray, NSInteger, NSUInteger};
|
||||||
use crate::dragdrop::DragInfo;
|
use crate::dragdrop::DragInfo;
|
||||||
use crate::listview::{
|
use crate::listview::{
|
||||||
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
|
LISTVIEW_DELEGATE_PTR,
|
||||||
ListViewDelegate, RowEdge
|
ListViewDelegate, RowEdge
|
||||||
};
|
};
|
||||||
use crate::utils::load;
|
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 {
|
pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instance: &T) -> *const Class {
|
||||||
load_or_register_class("NSTableView", instance.subclass_name(), |decl| unsafe {
|
load_or_register_class("NSTableView", instance.subclass_name(), |decl| unsafe {
|
||||||
decl.add_ivar::<usize>(LISTVIEW_DELEGATE_PTR);
|
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);
|
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,8 @@ use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
use crate::scrollview::ScrollView;
|
use crate::scrollview::ScrollView;
|
||||||
use crate::utils::CGSize;
|
use crate::utils::{os, CellFactory, CGSize};
|
||||||
|
use crate::view::ViewDelegate;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
use crate::macos::menu::MenuItem;
|
use crate::macos::menu::MenuItem;
|
||||||
|
@ -83,7 +84,6 @@ mod actions;
|
||||||
pub use actions::{RowAction, RowActionStyle};
|
pub use actions::{RowAction, RowActionStyle};
|
||||||
|
|
||||||
pub(crate) static LISTVIEW_DELEGATE_PTR: &str = "rstListViewDelegatePtr";
|
pub(crate) static LISTVIEW_DELEGATE_PTR: &str = "rstListViewDelegatePtr";
|
||||||
pub(crate) static LISTVIEW_CELL_VENDOR_PTR: &str = "rstListViewCellVendorPtr";
|
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
@ -91,57 +91,6 @@ use std::sync::{Arc, RwLock};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
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.
|
/// A helper method for instantiating view classes and applying default settings to them.
|
||||||
fn common_init(class: *const Class) -> id {
|
fn common_init(class: *const Class) -> id {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -232,7 +181,7 @@ pub struct ListView<T = ()> {
|
||||||
/// allocation and reuse, which is necessary for an "infinite" listview.
|
/// allocation and reuse, which is necessary for an "infinite" listview.
|
||||||
cell_factory: CellFactory,
|
cell_factory: CellFactory,
|
||||||
|
|
||||||
pub menu: PropertyNullable<Vec<MenuItem>>,
|
menu: PropertyNullable<Vec<MenuItem>>,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ShareId<Object>,
|
pub objc: ShareId<Object>,
|
||||||
|
@ -277,7 +226,7 @@ impl Default for ListView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ListView {
|
impl ListView {
|
||||||
/// Returns a default `View`, suitable for
|
/// @TODO: The hell is this for?
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let class = register_listview_class();
|
let class = register_listview_class();
|
||||||
let view = common_init(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 view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
||||||
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
let delegate_ptr: *const T = &*delegate;
|
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_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, setDelegate:view];
|
||||||
let _: () = msg_send![view, setDataSource: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) {
|
pub fn set_style(&self, style: crate::foundation::NSInteger) {
|
||||||
unsafe {
|
if os::is_minimum_version(11) {
|
||||||
let _: () = msg_send![&*self.objc, setStyle:style];
|
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) {
|
pub fn set_allows_empty_selection(&self, allows: bool) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setAllowsEmptySelection:match allows {
|
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) {
|
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setSelectionHighlightStyle:style];
|
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) {
|
pub fn select_row_indexes(&self, indexes: &[usize], extends_existing: bool) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
|
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) {
|
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
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) {
|
pub fn insert_rows(&self, indexes: &[usize], animation: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -514,6 +488,7 @@ impl<T> ListView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reload the rows at the specified indexes.
|
||||||
pub fn reload_rows(&self, indexes: &[usize]) {
|
pub fn reload_rows(&self, indexes: &[usize]) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
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) {
|
pub fn remove_rows(&self, indexes: &[usize], animations: RowAnimation) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
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) {
|
pub fn reload(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, reloadData];
|
let _: () = msg_send![&*self.objc, reloadData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the selected row.
|
||||||
pub fn get_selected_row_index(&self) -> NSInteger {
|
pub fn get_selected_row_index(&self) -> NSInteger {
|
||||||
unsafe { msg_send![&*self.objc, selectedRow] }
|
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 {
|
pub fn get_clicked_row_index(&self) -> NSInteger {
|
||||||
unsafe { msg_send![&*self.objc, clickedRow] }
|
unsafe { msg_send![&*self.objc, clickedRow] }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
|
||||||
/// application.
|
/// application.
|
||||||
pub struct App<T = (), M = ()> {
|
pub struct App<T = (), M = ()> {
|
||||||
|
/// The underlying Objective-C Object.
|
||||||
pub objc: Id<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>,
|
pub objc_delegate: Id<Object>,
|
||||||
|
|
||||||
|
/// The stored `AppDelegate`.
|
||||||
pub delegate: Box<T>,
|
pub delegate: Box<T>,
|
||||||
|
|
||||||
|
/// The main-thread AutoReleasePool. Drains on app exit.
|
||||||
pub pool: AutoReleasePool,
|
pub pool: AutoReleasePool,
|
||||||
|
|
||||||
_message: std::marker::PhantomData<M>
|
_message: std::marker::PhantomData<M>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,91 @@
|
||||||
|
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum CursorType {
|
pub enum CursorType {
|
||||||
|
/// A standard arrow.
|
||||||
Arrow,
|
Arrow,
|
||||||
|
|
||||||
|
/// A crosshair.
|
||||||
Crosshair,
|
Crosshair,
|
||||||
|
|
||||||
|
/// A closed hand, typically for mousedown and drag.
|
||||||
ClosedHand,
|
ClosedHand,
|
||||||
|
|
||||||
|
/// An open hand, typically for indicating draggable.
|
||||||
OpenHand,
|
OpenHand,
|
||||||
|
|
||||||
|
/// A pointing hand, like clicking a link.
|
||||||
PointingHand,
|
PointingHand,
|
||||||
|
|
||||||
|
/// Indicator that something can be resized to the left.
|
||||||
ResizeLeft,
|
ResizeLeft,
|
||||||
|
|
||||||
|
/// Indicator that something can be resized to the right.
|
||||||
ResizeRight,
|
ResizeRight,
|
||||||
|
|
||||||
|
/// Indicator that something can be resized on the horizontal axis.
|
||||||
ResizeLeftRight,
|
ResizeLeftRight,
|
||||||
|
|
||||||
|
/// Indicates that something can be resized up.
|
||||||
ResizeUp,
|
ResizeUp,
|
||||||
|
|
||||||
|
/// Indicates that something can be resized down.
|
||||||
ResizeDown,
|
ResizeDown,
|
||||||
|
|
||||||
|
/// Indicator that something can be resized on the vertical axis.
|
||||||
ResizeUpDown,
|
ResizeUpDown,
|
||||||
|
|
||||||
|
/// Otherwise known as the "poof" or "cloud" cursor. Indicates something will vanish, like
|
||||||
|
/// dragging into the Trash.
|
||||||
DisappearingItem,
|
DisappearingItem,
|
||||||
|
|
||||||
|
/// Indicate an insertion point, like for text.
|
||||||
IBeam,
|
IBeam,
|
||||||
|
|
||||||
|
/// The vertical version of `CursorType::IBeam`.
|
||||||
IBeamVertical,
|
IBeamVertical,
|
||||||
|
|
||||||
|
/// Indicates an operation is illegal.
|
||||||
OperationNotAllowed,
|
OperationNotAllowed,
|
||||||
|
|
||||||
|
/// The drag link cursor.
|
||||||
DragLink,
|
DragLink,
|
||||||
|
|
||||||
|
/// Used for drag-and-drop usually, will displayu the standard "+" icon next to the cursor.
|
||||||
DragCopy,
|
DragCopy,
|
||||||
|
|
||||||
|
/// Indicates a context menu will open.
|
||||||
ContextMenu
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Cursor;
|
pub struct Cursor;
|
||||||
|
|
||||||
|
@ -82,4 +139,19 @@ impl Cursor {
|
||||||
let _: () = msg_send![class!(NSCursor), unhide];
|
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
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,54 @@
|
||||||
//! A lightweight wrapper over some networking components, like `NSURLRequest` and co.
|
//! 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::{msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::{id, NSString};
|
use crate::foundation::{id, NSString};
|
||||||
|
|
||||||
|
/// A wrapper around `NSURLRequest`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct URLRequest {
|
pub struct URLRequest(ShareId<Object>);
|
||||||
pub inner: Id<Object>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl URLRequest {
|
impl URLRequest {
|
||||||
pub fn with(inner: id) -> Self {
|
/// Wraps and retains an `NSURLRequest`.
|
||||||
URLRequest {
|
pub fn with(request: id) -> Self {
|
||||||
inner: unsafe { Id::from_ptr(inner) }
|
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 {
|
NSString::from_retained(unsafe {
|
||||||
let url: id = msg_send![&*self.inner, URL];
|
let url: id = msg_send![&*self.0, URL];
|
||||||
msg_send![url, absoluteString]
|
msg_send![url, absoluteString]
|
||||||
}).to_string()
|
}).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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
pub trait Dispatcher {
|
||||||
|
/// The type of Message you're sending. This should be lightweight and thread safe.
|
||||||
type Message: Send + Sync;
|
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) {}
|
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) {}
|
fn on_background_message(&self, _message: Self::Message) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
|
||||||
}
|
|
|
@ -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_id::ShareId;
|
||||||
use objc::runtime::{Class, Object};
|
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::foundation::{id, nil, YES, NO, NSUInteger};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
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")]
|
#[cfg(target_os = "ios")]
|
||||||
mod ios;
|
mod ios;
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
use ios::{register_progress_indicator_class};
|
use ios::register_progress_indicator_class;
|
||||||
|
|
||||||
mod enums;
|
mod enums;
|
||||||
pub use enums::ProgressIndicatorStyle;
|
pub use enums::ProgressIndicatorStyle;
|
||||||
|
|
||||||
|
/// A control used for reporting progress to a user visually.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ProgressIndicator {
|
pub struct ProgressIndicator {
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C Object.
|
||||||
pub objc: ShareId<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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
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
|
pub center_y: LayoutAnchorY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,13 +68,15 @@ impl Default for ProgressIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new() -> Self {
|
||||||
let view = unsafe {
|
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];
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(feature = "macos")]
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
|
||||||
view
|
view
|
||||||
|
@ -85,17 +97,6 @@ impl ProgressIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
/// Starts the animation for an indeterminate indicator.
|
||||||
pub fn start_animation(&self) {
|
pub fn start_animation(&self) {
|
||||||
unsafe {
|
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) {
|
pub fn stop_animation(&self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, stopAnimation:nil];
|
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 {
|
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) {
|
pub fn set_style(&self, style: ProgressIndicatorStyle) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let style = style as NSUInteger;
|
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) {
|
pub fn set_indeterminate(&self, is_indeterminate: bool) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _: () = msg_send![&*self.objc, setIndeterminate:match is_indeterminate {
|
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 {
|
impl Layout for ProgressIndicator {
|
||||||
|
@ -147,20 +177,19 @@ impl Layout for ProgressIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop 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
|
/// 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).
|
/// 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.
|
/// There are, thankfully, no delegates we need to break here.
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
/*if self.delegate.is_some() {
|
unsafe {
|
||||||
unsafe {
|
let superview: id = msg_send![&*self.objc, superview];
|
||||||
let superview: id = msg_send![&*self.objc, superview];
|
if superview != nil {
|
||||||
if superview != nil {
|
let _: () = msg_send![&*self.objc, removeFromSuperview];
|
||||||
let _: () = msg_send![&*self.objc, removeFromSuperview];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::dragdrop::{DragInfo, DragOperation};
|
use crate::dragdrop::{DragInfo, DragOperation};
|
||||||
use crate::scrollview::ScrollView;
|
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 {
|
pub trait ScrollViewDelegate {
|
||||||
/// Called when the View is ready to work with. You're passed a `View` - this is safe to
|
/// 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
|
/// store and use repeatedly, but it's not thread safe - any UI calls must be made from the
|
||||||
|
|
|
@ -18,6 +18,7 @@ use crate::utils::load;
|
||||||
/// where our `NSSwitch` lives.
|
/// where our `NSSwitch` lives.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Switch {
|
pub struct Switch {
|
||||||
|
/// A pointer to the underlying Objective-C Object.
|
||||||
pub objc: ShareId<Object>,
|
pub objc: ShareId<Object>,
|
||||||
handler: Option<TargetActionHandler>,
|
handler: Option<TargetActionHandler>,
|
||||||
|
|
||||||
|
|
|
@ -223,7 +223,8 @@ impl<T> Label<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call this to set the text for the label.
|
/// 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);
|
let s = NSString::new(text);
|
||||||
|
|
||||||
unsafe {
|
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.
|
/// Set the line break mode for this label.
|
||||||
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
|
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
unsafe {
|
unsafe {
|
||||||
let cell: id = msg_send![&*self.objc, cell];
|
let cell: id = msg_send![&*self.objc, cell];
|
||||||
let mode = mode as NSUInteger;
|
let mode = mode as NSUInteger;
|
||||||
|
let _: () = msg_send![cell, setTruncatesLastVisibleLine:YES];
|
||||||
let _: () = msg_send![cell, setLineBreakMode:mode];
|
let _: () = msg_send![cell, setLineBreakMode:mode];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
71
src/utils/cell_factory.rs
Normal file
71
src/utils/cell_factory.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,9 @@ use objc_id::ShareId;
|
||||||
|
|
||||||
use crate::foundation::{id, BOOL, YES, NO};
|
use crate::foundation::{id, BOOL, YES, NO};
|
||||||
|
|
||||||
|
mod cell_factory;
|
||||||
|
pub use cell_factory::CellFactory;
|
||||||
|
|
||||||
pub mod os;
|
pub mod os;
|
||||||
|
|
||||||
/// A generic trait that's used throughout multiple different controls in this framework - acts as
|
/// A generic trait that's used throughout multiple different controls in this framework - acts as
|
||||||
|
|
|
@ -3,14 +3,22 @@ use objc::runtime::Object;
|
||||||
use objc::{class, msg_send, sel, sel_impl};
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, NSString};
|
use crate::foundation::{id, nil, NSString};
|
||||||
use crate::layout::{Layout};
|
use crate::layout::Layout;
|
||||||
use crate::macos::toolbar::ToolbarItem;
|
use crate::macos::toolbar::ToolbarItem;
|
||||||
use crate::view::{View, ViewController, ViewDelegate};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct SplitViewItem<T> {
|
pub struct SplitViewItem<T> {
|
||||||
|
/// The underlying Objective-C Object.
|
||||||
pub objc: ShareId<Object>,
|
pub objc: ShareId<Object>,
|
||||||
|
|
||||||
|
/// The wrapped ViewController.
|
||||||
pub view_controller: ViewController<T>
|
pub view_controller: ViewController<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,65 +26,128 @@ impl<T> SplitViewItem<T>
|
||||||
where
|
where
|
||||||
T: ViewDelegate + 'static
|
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 {
|
pub fn item(view: T) -> Self {
|
||||||
let view_controller = ViewController::new(view);
|
let view_controller = ViewController::new(view);
|
||||||
|
|
||||||
let objc = unsafe {
|
|
||||||
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), splitViewItemWithViewController:&*view_controller.objc])
|
|
||||||
};
|
|
||||||
|
|
||||||
SplitViewItem {
|
SplitViewItem {
|
||||||
objc,
|
objc: unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSSplitViewItem),
|
||||||
|
splitViewItemWithViewController:&*view_controller.objc
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
view_controller
|
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 {
|
pub fn sidebar(view: T) -> Self {
|
||||||
|
if !os::is_minimum_version(11) {
|
||||||
|
return Self::item(view);
|
||||||
|
}
|
||||||
|
|
||||||
let view_controller = ViewController::new(view);
|
let view_controller = ViewController::new(view);
|
||||||
|
|
||||||
let objc = unsafe {
|
|
||||||
ShareId::from_ptr(msg_send![class!(NSSplitViewItem), sidebarWithViewController:&*view_controller.objc])
|
|
||||||
};
|
|
||||||
|
|
||||||
SplitViewItem {
|
SplitViewItem {
|
||||||
objc,
|
objc: unsafe {
|
||||||
|
ShareId::from_ptr(msg_send![class!(NSSplitViewItem),
|
||||||
|
sidebarWithViewController:&*view_controller.objc
|
||||||
|
])
|
||||||
|
},
|
||||||
|
|
||||||
view_controller
|
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) {
|
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
||||||
unsafe {
|
if os::is_minimum_version(11) {
|
||||||
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
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)]
|
#[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>,
|
pub objc: ShareId<Object>,
|
||||||
|
|
||||||
|
/// A reference to the sidebar `SplitViewItem`.
|
||||||
pub sidebar: SplitViewItem<Sidebar>,
|
pub sidebar: SplitViewItem<Sidebar>,
|
||||||
|
|
||||||
|
/// A reference to the content `SplitViewItem`.
|
||||||
pub content: SplitViewItem<Content>,
|
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
|
where
|
||||||
Sidebar: ViewDelegate + 'static,
|
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 sidebar = SplitViewItem::sidebar(sidebar);
|
||||||
let content = SplitViewItem::item(content);
|
let content = SplitViewItem::item(content);
|
||||||
|
|
||||||
|
let details = match details {
|
||||||
|
Some(vc) => Some(SplitViewItem::item(vc)),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
let objc = unsafe {
|
let objc = unsafe {
|
||||||
let vc: id = msg_send![class!(NSSplitViewController), new];
|
let vc: id = msg_send![class!(NSSplitViewController), new];
|
||||||
let _: () = msg_send![vc, addSplitViewItem:&*sidebar.objc];
|
let _: () = msg_send![vc, addSplitViewItem:&*sidebar.objc];
|
||||||
let _: () = msg_send![vc, addSplitViewItem:&*content.objc];
|
let _: () = msg_send![vc, addSplitViewItem:&*content.objc];
|
||||||
|
|
||||||
|
if let Some(details) = &details {
|
||||||
|
let _: () = msg_send![vc, addSplitViewItem:&*details.objc];
|
||||||
|
}
|
||||||
|
|
||||||
ShareId::from_ptr(vc)
|
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
|
/// 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
|
/// (which is highly unlikely, unless you went out of your way to duck this) then it will do
|
||||||
/// nothing.
|
/// 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) {
|
pub fn set_autosave_name(&self, name: &str) {
|
||||||
let name = NSString::new(name);
|
let name = NSString::new(name);
|
||||||
|
|
||||||
|
@ -94,29 +169,9 @@ where
|
||||||
let _: () = msg_send![split_view, setAutosaveName:&*name];
|
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> {
|
fn get_backing_node(&self) -> ShareId<Object> {
|
||||||
self.objc.clone()
|
self.objc.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,8 @@ impl From<id> for OpenPanelParameters {
|
||||||
match msg_send![params, allowsDirectories] {
|
match msg_send![params, allowsDirectories] {
|
||||||
YES => true,
|
YES => true,
|
||||||
NO => false,
|
NO => false,
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsDirectories"); }
|
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsDirectories"); }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -64,6 +66,8 @@ impl From<id> for OpenPanelParameters {
|
||||||
match msg_send![params, allowsMultipleSelection] {
|
match msg_send![params, allowsMultipleSelection] {
|
||||||
YES => true,
|
YES => true,
|
||||||
NO => false,
|
NO => false,
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "aarch64"))]
|
||||||
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsMultipleSelection"); }
|
_ => { panic!("Invalid value from WKOpenPanelParameters:allowsMultipleSelection"); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,8 +47,8 @@ extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_me
|
||||||
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
|
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let name = NSString::wrap(msg_send![script_message, name]);
|
let name = NSString::from_retained(msg_send![script_message, name]);
|
||||||
let body = NSString::wrap(msg_send![script_message, body]);
|
let body = NSString::from_retained(msg_send![script_message, body]);
|
||||||
delegate.on_message(name.to_str(), body.to_str());
|
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) => {
|
Some(u) => {
|
||||||
let nsurls: NSArray = u.iter().map(|s| {
|
let nsurls: NSArray = u.iter().map(|s| {
|
||||||
let s = NSString::new(s);
|
let s = NSString::new(s);
|
||||||
msg_send![class!(NSURL), URLWithString:s.into_inner()]
|
msg_send![class!(NSURL), URLWithString:&*s]
|
||||||
}).collect::<Vec<id>>().into();
|
}).collect::<Vec<id>>().into();
|
||||||
|
|
||||||
(*handler).call((nsurls.into_inner(),));
|
(*handler).call((nsurls.into(),));
|
||||||
},
|
},
|
||||||
|
|
||||||
None => { (*handler).call((nil,)); }
|
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
|
/// 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
|
/// response is upgraded to BecomeDownload. Only called when explicitly linked since it's a private
|
||||||
/// API.
|
/// 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) {
|
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 delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
|
||||||
|
|
||||||
let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
|
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 {
|
delegate.run_save_panel(filename.to_str(), move |can_overwrite, path| unsafe {
|
||||||
if path.is_none() {
|
if path.is_none() {
|
||||||
|
@ -119,7 +119,7 @@ extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: i
|
||||||
(*handler).call((match can_overwrite {
|
(*handler).call((match can_overwrite {
|
||||||
true => YES,
|
true => YES,
|
||||||
false => NO
|
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.
|
// 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
|
// 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.
|
// 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));
|
decl.add_method(sel!(_download:decideDestinationWithSuggestedFilename:completionHandler:), handle_download::<T> as extern fn(&Object, _, id, id, usize));
|
||||||
|
|
||||||
VIEW_CLASS = decl.register();
|
VIEW_CLASS = decl.register();
|
||||||
|
|
|
@ -10,6 +10,7 @@ use crate::webview::enums::InjectAt;
|
||||||
|
|
||||||
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime
|
/// A wrapper for `WKWebViewConfiguration`. Holds (retains) pointers for the Objective-C runtime
|
||||||
/// where everything lives.
|
/// where everything lives.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct WebViewConfig {
|
pub struct WebViewConfig {
|
||||||
pub objc: Id<Object>,
|
pub objc: Id<Object>,
|
||||||
pub handlers: Vec<String>
|
pub handlers: Vec<String>
|
||||||
|
|
|
@ -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
|
/// 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.
|
/// if you opt in via the `webview-downloading` feature.
|
||||||
#[cfg(feature = "webview-downloading")]
|
#[cfg(feature = "webview-downloading-macos")]
|
||||||
BecomeDownload
|
BecomeDownload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ impl From<NavigationResponsePolicy> for NSInteger {
|
||||||
NavigationResponsePolicy::Cancel => 0,
|
NavigationResponsePolicy::Cancel => 0,
|
||||||
NavigationResponsePolicy::Allow => 1,
|
NavigationResponsePolicy::Allow => 1,
|
||||||
|
|
||||||
#[cfg(feature = "webview-downloading")]
|
#[cfg(feature = "webview-downloading-macos")]
|
||||||
NavigationResponsePolicy::BecomeDownload => 2
|
NavigationResponsePolicy::BecomeDownload => 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,9 @@ fn allocate_webview(
|
||||||
|
|
||||||
if let Some(delegate) = &objc_delegate {
|
if let Some(delegate) = &objc_delegate {
|
||||||
// Technically private!
|
// Technically private!
|
||||||
#[cfg(feature = "webview-downloading")]
|
#[cfg(feature = "webview-downloading-macos")]
|
||||||
let process_pool: id = msg_send![configuration, processPool];
|
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 _: () = msg_send![process_pool, _setDownloadDelegate:*delegate];
|
||||||
|
|
||||||
let content_controller: id = msg_send![configuration, userContentController];
|
let content_controller: id = msg_send![configuration, userContentController];
|
||||||
|
@ -203,7 +203,7 @@ impl<T> WebView<T> {
|
||||||
let url = NSString::new(url);
|
let url = NSString::new(url);
|
||||||
|
|
||||||
unsafe {
|
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 request: id = msg_send![class!(NSURLRequest), requestWithURL:u];
|
||||||
let _: () = msg_send![&*self.objc, loadRequest:request];
|
let _: () = msg_send![&*self.objc, loadRequest:request];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue