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.
|
||||
#[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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ()>
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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] }
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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::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];
|
||||
}
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
||||
|
|
|
@ -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
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};
|
||||
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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"); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue