A large smattering of updates.
- Added basic animation support, via NSAnimationContext proxy objects. These can be used to animate layout constraints and alpha values, currently. - Fixed a bug in ListView where the underlying NSTableView would not redraw the full correct virtual height in some conditions. - Added safe layout guide support to some views. - Added a new trait to buffer ObjC object access for view and control types. This is the supertrait of the Layout and Control traits. - Added a Control trait, which implements various NSControl pieces. - Added a Select control, which is a Select-ish HTML dropdown lookalike. - Added NSURL support, which is one of the few types to expose here. - Filesystem and pasteboard types now work with NSURLs. Users who need pathbufs can use the provided conversion method on NSURL. - Fixed a bug where some Window and ViewController types could wind up in a double-init scenario.
This commit is contained in:
parent
87533d576f
commit
4ecfbd0928
|
@ -23,14 +23,14 @@ block = "0.1.6"
|
|||
core-foundation = { version = "0.9", features = ["with-chrono"] }
|
||||
core-graphics = "0.22"
|
||||
dispatch = "0.2.0"
|
||||
infer = { version = "0.4", optional = true }
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
objc = "0.2.7"
|
||||
objc_id = "0.1.1"
|
||||
os_info = "3.0.1"
|
||||
uuid = { version = "0.8", features = ["v4"], optional = true }
|
||||
url = "2.1.1"
|
||||
infer = { version = "0.4", optional = true }
|
||||
uuid = { version = "0.8", features = ["v4"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
eval = "0.4"
|
||||
|
|
73
src/appkit/animation.rs
Normal file
73
src/appkit/animation.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use block::ConcreteBlock;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::id;
|
||||
|
||||
/// A very, very basic wrapper around NSAnimationContext. 100% subject to change.
|
||||
#[derive(Debug)]
|
||||
pub struct AnimationContext(id);
|
||||
|
||||
impl AnimationContext {
|
||||
/// Wraps an NSAnimationContext pointer.
|
||||
pub fn new(ctx: id) -> Self {
|
||||
Self(ctx)
|
||||
}
|
||||
|
||||
/// Sets the animation duration.
|
||||
pub fn set_duration(&mut self, duration: f64) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.0, setDuration:duration];
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass it a block, and the changes in that block will be animated, provided they're
|
||||
/// properties that support animation.
|
||||
///
|
||||
/// [https://developer.apple.com/documentation/appkit/nsanimationcontext?language=objc]
|
||||
///
|
||||
/// For more information, you should consult the documentation for NSAnimationContext, then skim
|
||||
/// the supported methods here.
|
||||
pub fn run<F>(animation: F)
|
||||
where
|
||||
F: Fn(&mut AnimationContext) + Send + Sync + 'static
|
||||
{
|
||||
let block = ConcreteBlock::new(move |ctx| {
|
||||
let mut context = AnimationContext(ctx);
|
||||
animation(&mut context);
|
||||
});
|
||||
let block = block.copy();
|
||||
|
||||
unsafe {
|
||||
//let context: id = msg_send![class!(NSAnimationContext), currentContext];
|
||||
let _: () = msg_send![class!(NSAnimationContext), runAnimationGroup:block];
|
||||
}
|
||||
}
|
||||
|
||||
/// Pass it a block, and the changes in that block will be animated, provided they're
|
||||
/// properties that support animation.
|
||||
///
|
||||
/// [https://developer.apple.com/documentation/appkit/nsanimationcontext?language=objc]
|
||||
///
|
||||
/// For more information, you should consult the documentation for NSAnimationContext, then skim
|
||||
/// the supported methods here.
|
||||
pub fn run_with_completion_handler<F, C>(animation: F, completion_handler: C)
|
||||
where
|
||||
F: Fn(&mut AnimationContext) + Send + Sync + 'static,
|
||||
C: Fn() + Send + Sync + 'static
|
||||
{
|
||||
let block = ConcreteBlock::new(move |ctx| {
|
||||
let mut context = AnimationContext(ctx);
|
||||
animation(&mut context);
|
||||
});
|
||||
let block = block.copy();
|
||||
|
||||
let completion_block = ConcreteBlock::new(completion_handler);
|
||||
let completion_block = completion_block.copy();
|
||||
|
||||
unsafe {
|
||||
//let context: id = msg_send![class!(NSAnimationContext), currentContext];
|
||||
let _: () = msg_send![class!(NSAnimationContext), runAnimationGroup:block
|
||||
completionHandler:completion_block];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -49,6 +49,8 @@ use crate::appkit::menu::Menu;
|
|||
use crate::notification_center::Dispatcher;
|
||||
use crate::utils::activate_cocoa_multithreading;
|
||||
|
||||
//use crate::bundle::set_bundle_id;
|
||||
|
||||
mod class;
|
||||
use class::register_app_class;
|
||||
|
||||
|
@ -131,7 +133,7 @@ impl<T> App<T> where T: AppDelegate + 'static {
|
|||
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
||||
/// Objective-C side of things.
|
||||
pub fn new(_bundle_id: &str, delegate: T) -> Self {
|
||||
// set_bundle_id(bundle_id);
|
||||
//set_bundle_id(bundle_id);
|
||||
|
||||
activate_cocoa_multithreading();
|
||||
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
mod alert;
|
||||
pub use alert::Alert;
|
||||
|
||||
mod animation;
|
||||
pub use animation::AnimationContext;
|
||||
|
||||
mod app;
|
||||
pub use app::*;
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ pub use traits::ToolbarDelegate;
|
|||
mod enums;
|
||||
pub use enums::{ToolbarDisplayMode, ToolbarSizeMode, ItemIdentifier};
|
||||
|
||||
pub(crate) static TOOLBAR_PTR: &str = "rstToolbarPtr";
|
||||
pub(crate) static TOOLBAR_PTR: &str = "cacaoToolbarPtr";
|
||||
|
||||
/// A wrapper for `NSToolbar`. Holds (retains) pointers for the Objective-C runtime
|
||||
/// where our `NSToolbar` and associated delegate live.
|
||||
|
@ -62,7 +62,7 @@ impl<T> Toolbar<T> where T: ToolbarDelegate + 'static {
|
|||
(ShareId::from_ptr(toolbar), ShareId::from_ptr(objc_delegate))
|
||||
};
|
||||
|
||||
&mut delegate.did_load(Toolbar {
|
||||
let _ret = &mut delegate.did_load(Toolbar {
|
||||
objc: objc.clone(),
|
||||
objc_delegate: objc_delegate.clone(),
|
||||
identifier: identifier.clone(),
|
||||
|
|
|
@ -54,7 +54,7 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
|
|||
/// Allocates and configures an `NSWindowController` in the Objective-C/Cocoa runtime that maps over
|
||||
/// to your supplied delegate.
|
||||
pub fn with(config: WindowConfig, delegate: T) -> Self {
|
||||
let mut window = Window::with(config, delegate);
|
||||
let window = Window::with(config, delegate);
|
||||
|
||||
let objc = unsafe {
|
||||
let window_controller_class = register_window_controller_class::<T>();
|
||||
|
@ -69,13 +69,6 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
|
|||
Id::from_ptr(controller)
|
||||
};
|
||||
|
||||
if let Some(delegate) = &mut window.delegate {
|
||||
(*delegate).did_load(Window {
|
||||
delegate: None,
|
||||
objc: window.objc.clone()
|
||||
});
|
||||
}
|
||||
|
||||
WindowController { objc, window }
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,11 @@ use objc::{msg_send, sel, sel_impl, class};
|
|||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::appkit::toolbar::{Toolbar, ToolbarDelegate};
|
||||
use crate::color::Color;
|
||||
use crate::foundation::{id, nil, to_bool, YES, NO, NSString, NSInteger, NSUInteger};
|
||||
use crate::layout::traits::Layout;
|
||||
use crate::appkit::toolbar::{Toolbar, ToolbarDelegate};
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::{os, Controller};
|
||||
|
||||
mod class;
|
||||
|
@ -289,7 +290,7 @@ impl<T> Window<T> {
|
|||
|
||||
/// Given a view, sets it as the content view for this window.
|
||||
pub fn set_content_view<L: Layout + 'static>(&self, view: &L) {
|
||||
view.with_backing_node(|backing_node| unsafe {
|
||||
view.with_backing_obj_mut(|backing_node| unsafe {
|
||||
let _: () = msg_send![&*self.objc, setContentView:&*backing_node];
|
||||
});
|
||||
}
|
||||
|
@ -446,6 +447,7 @@ impl<T> Window<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the separator style for this window.
|
||||
pub fn set_titlebar_separator_style(&self, style: crate::foundation::NSInteger) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, setTitlebarSeparatorStyle:style];
|
||||
|
|
|
@ -66,9 +66,7 @@ extern fn get_bundle_id(this: &Object, s: Sel, v: id) -> id {
|
|||
let url: id = msg_send![main_bundle, bundleURL];
|
||||
let x: id = msg_send![url, absoluteString];
|
||||
println!("Got here? {:?}", x);
|
||||
unsafe {
|
||||
NSString::alloc(nil).init_str("com.secretkeys.subatomic")
|
||||
}
|
||||
NSString::new("com.test.user_notifications").into()
|
||||
} else {
|
||||
msg_send![this, __bundleIdentifier]
|
||||
}
|
||||
|
|
|
@ -29,10 +29,13 @@ use objc::runtime::{Class, Object, Sel};
|
|||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::control::Control;
|
||||
use crate::image::Image;
|
||||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::keys::Key;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::text::{AttributedString, Font};
|
||||
use crate::utils::{load, properties::ObjcProperty};
|
||||
|
||||
|
@ -213,11 +216,21 @@ 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);
|
||||
pub fn set_key_equivalent<'a, K>(&self, key: K)
|
||||
where
|
||||
K: Into<Key<'a>>
|
||||
{
|
||||
let key: Key<'a> = key.into();
|
||||
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setKeyEquivalent:&*key];
|
||||
self.objc.with_mut(|obj| {
|
||||
let keychar = match key {
|
||||
Key::Char(s) => NSString::new(s),
|
||||
Key::Delete => NSString::new("\u{08}")
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let _: () = msg_send![obj, setKeyEquivalent:&*keychar];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -281,26 +294,32 @@ impl Button {
|
|||
}
|
||||
}
|
||||
|
||||
impl Layout for Button {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl ObjcAccess for Button {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for &Button {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl Layout for Button {}
|
||||
impl Control for Button {}
|
||||
|
||||
impl ObjcAccess for &Button {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for &Button {}
|
||||
impl Control for &Button {}
|
||||
|
||||
impl Drop for Button {
|
||||
/// Nils out references on the Objective-C side and removes this from the backing view.
|
||||
// Just to be sure, let's... nil these out. They should be weak references,
|
||||
|
|
|
@ -217,6 +217,7 @@ pub enum Color {
|
|||
/// The default color to use for thin separators/lines that
|
||||
/// do not allow content underneath to be visible.
|
||||
/// This value automatically switches to the correct variant depending on light or dark mode.
|
||||
#[cfg(feature = "uikit")]
|
||||
OpaqueSeparator,
|
||||
|
||||
/// The default color to use for rendering links.
|
||||
|
@ -495,7 +496,10 @@ unsafe fn to_objc(obj: &Color) -> id {
|
|||
Color::SystemBackgroundSecondary => system_color_with_fallback!(color, secondarySystemBackgroundColor, clearColor),
|
||||
Color::SystemBackgroundTertiary => system_color_with_fallback!(color, tertiarySystemBackgroundColor, clearColor),
|
||||
Color::Separator => system_color_with_fallback!(color, separatorColor, lightGrayColor),
|
||||
|
||||
#[cfg(feature = "uikit")]
|
||||
Color::OpaqueSeparator => system_color_with_fallback!(color, opaqueSeparatorColor, darkGrayColor),
|
||||
|
||||
Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
|
||||
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
|
||||
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
|
||||
|
|
56
src/control/mod.rs
Normal file
56
src/control/mod.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
|
||||
use crate::foundation::{id, YES, NO, NSUInteger};
|
||||
use crate::objc_access::ObjcAccess;
|
||||
|
||||
/// Use this enum for specifying NSControl size types.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ControlSize {
|
||||
/// The smallest control size.
|
||||
Mini,
|
||||
|
||||
/// A smaller control size.
|
||||
Small,
|
||||
|
||||
/// The default, regular, size.
|
||||
Regular,
|
||||
|
||||
/// A large control. Only available on macOS 11.0+.
|
||||
/// If you pass this to the `set_control_size` method on the `Control` trait, it will
|
||||
/// transparently map to `Regular` on 10.15 and below.
|
||||
Large
|
||||
}
|
||||
|
||||
/// A trait that view wrappers must conform to. Enables managing the subview tree.
|
||||
#[allow(unused_variables)]
|
||||
pub trait Control: ObjcAccess {
|
||||
/// Whether this control is enabled or not.
|
||||
fn set_enabled(&self, is_enabled: bool) {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setEnabled:match is_enabled {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the underlying control size.
|
||||
fn set_control_size(&self, size: ControlSize) {
|
||||
let control_size: NSUInteger = match size {
|
||||
ControlSize::Mini => 2,
|
||||
ControlSize::Small => 1,
|
||||
ControlSize::Regular => 0,
|
||||
|
||||
ControlSize::Large => match crate::utils::os::is_minimum_version(11) {
|
||||
true => 3,
|
||||
false => 0
|
||||
}
|
||||
};
|
||||
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setControlSize:control_size];
|
||||
});
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::{id, YES, NO, NSInteger, NSString};
|
||||
use crate::foundation::{id, nil, YES, NO, NSInteger, NSString, NSURL};
|
||||
use crate::filesystem::enums::ModalResponse;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
|
@ -127,8 +127,7 @@ impl FileSelectPanel {
|
|||
self.allows_multiple_selection = allows;
|
||||
}
|
||||
|
||||
/// Shows the panel as a modal. Currently sheets are not supported, but you're free (and able
|
||||
/// to) thread the Objective C calls yourself by using the panel field on this struct.
|
||||
/// Shows the panel as a modal.
|
||||
///
|
||||
/// Note that this clones the underlying `NSOpenPanel` pointer. This is theoretically safe as
|
||||
/// the system runs and manages that in another process, and we're still abiding by the general
|
||||
|
@ -138,7 +137,7 @@ impl FileSelectPanel {
|
|||
/// script) or can't easily pass one to use as a sheet.
|
||||
pub fn show<F>(&self, handler: F)
|
||||
where
|
||||
F: Fn(Vec<PathBuf>) + 'static
|
||||
F: Fn(Vec<NSURL>) + 'static
|
||||
{
|
||||
let panel = self.panel.clone();
|
||||
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||
|
@ -155,6 +154,16 @@ impl FileSelectPanel {
|
|||
}
|
||||
}
|
||||
|
||||
/// As panels descend behind the scenes from `NSWindow`, we can call through to close it.
|
||||
///
|
||||
/// You should really prefer to utilize sheets to display panels; this is offered as a
|
||||
/// convenience for rare cases where you might need to retain a panel and close it later on.
|
||||
pub fn close(&self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.panel, close];
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows the panel as a modal. Currently, this method accepts `Window`s which use a delegate.
|
||||
/// If you're using a `Window` without a delegate, you may need to opt to use the `show()`
|
||||
/// method.
|
||||
|
@ -164,7 +173,7 @@ impl FileSelectPanel {
|
|||
/// retain/ownership rules here.
|
||||
pub fn begin_sheet<T, F>(&self, window: &Window<T>, handler: F)
|
||||
where
|
||||
F: Fn(Vec<PathBuf>) + 'static
|
||||
F: Fn(Vec<NSURL>) + 'static
|
||||
{
|
||||
let panel = self.panel.clone();
|
||||
let completion = ConcreteBlock::new(move |result: NSInteger| {
|
||||
|
@ -185,15 +194,16 @@ impl FileSelectPanel {
|
|||
/// Retrieves the selected URLs from the provided panel.
|
||||
/// This is currently a bit ugly, but it's also not something that needs to be the best thing in
|
||||
/// the world as it (ideally) shouldn't be called repeatedly in hot spots.
|
||||
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
|
||||
///
|
||||
/// (We mostly do this to find the sweet spot between Rust constructs and necessary Foundation
|
||||
/// interaction patterns)
|
||||
fn get_urls(panel: &Object) -> Vec<NSURL> {
|
||||
unsafe {
|
||||
let urls: id = msg_send![&*panel, URLs];
|
||||
let count: usize = msg_send![urls, count];
|
||||
|
||||
(0..count).map(|index| {
|
||||
let url: id = msg_send![urls, objectAtIndex:index];
|
||||
let path = NSString::retain(msg_send![url, path]).to_string();
|
||||
path.into()
|
||||
NSURL::retain(msg_send![urls, objectAtIndex:index])
|
||||
}).collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,21 @@ impl NSData {
|
|||
}
|
||||
}
|
||||
|
||||
/// Given a slice of bytes, creates, retains, and returns a wrapped `NSData`.
|
||||
///
|
||||
/// This method is borrowed straight out of [objc-foundation](objc-foundation) by the amazing
|
||||
/// Steven Sheldon, and just tweaked slightly to fit the desired API semantics here.
|
||||
///
|
||||
/// [objc-foundation]: https://crates.io/crates/objc-foundation
|
||||
pub fn with_slice(bytes: &[u8]) -> Self {
|
||||
let bytes_ptr = bytes.as_ptr() as *mut c_void;
|
||||
|
||||
unsafe {
|
||||
let obj: id = msg_send![class!(NSData), dataWithBytes:bytes_ptr length:bytes.len()];
|
||||
NSData(Id::from_ptr(obj))
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a (presumably) `NSData`, wraps and retains it.
|
||||
pub fn retain(data: id) -> Self {
|
||||
NSData(unsafe {
|
||||
|
|
|
@ -42,6 +42,10 @@ pub use number::NSNumber;
|
|||
mod string;
|
||||
pub use string::NSString;
|
||||
|
||||
// Separate named module to not conflict with the `url` crate. Go figure.
|
||||
mod urls;
|
||||
pub use urls::{NSURL, NSURLBookmarkCreationOption, NSURLBookmarkResolutionOption};
|
||||
|
||||
/// Bool mapping types differ between ARM and x64. There's a number of places that we need to check
|
||||
/// against BOOL results throughout the framework, and this just simplifies some mismatches.
|
||||
#[inline(always)]
|
||||
|
|
66
src/foundation/urls/bookmark_options.rs
Normal file
66
src/foundation/urls/bookmark_options.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use crate::foundation::NSUInteger;
|
||||
|
||||
/// Options used when creating bookmark data.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum NSURLBookmarkCreationOption {
|
||||
/// Specifies that a bookmark created with this option should be created with minimal information.
|
||||
Minimal,
|
||||
|
||||
/// Specifies that the bookmark data should include properties required to create Finder alias files.
|
||||
SuitableForBookmarkFile,
|
||||
|
||||
/// Specifies that you want to create a security-scoped bookmark that, when resolved, provides a
|
||||
/// security-scoped URL allowing read/write access to a file-system resource.
|
||||
SecurityScoped,
|
||||
|
||||
/// When combined with the NSURLBookmarkCreationOptions::SecurityScoped option, specifies that you
|
||||
/// want to create a security-scoped bookmark that, when resolved, provides a security-scoped URL allowing
|
||||
/// read-only access to a file-system resource.
|
||||
SecurityScopedReadOnly
|
||||
}
|
||||
|
||||
impl From<NSURLBookmarkCreationOption> for NSUInteger {
|
||||
fn from(flag: NSURLBookmarkCreationOption) -> NSUInteger {
|
||||
match flag {
|
||||
NSURLBookmarkCreationOption::Minimal => 1u64 << 9,
|
||||
NSURLBookmarkCreationOption::SuitableForBookmarkFile => 1u64 << 10,
|
||||
NSURLBookmarkCreationOption::SecurityScoped => 1 << 11,
|
||||
NSURLBookmarkCreationOption::SecurityScopedReadOnly => 1 << 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&NSURLBookmarkCreationOption> for NSUInteger {
|
||||
fn from(flag: &NSURLBookmarkCreationOption) -> NSUInteger {
|
||||
match flag {
|
||||
NSURLBookmarkCreationOption::Minimal => 1u64 << 9,
|
||||
NSURLBookmarkCreationOption::SuitableForBookmarkFile => 1u64 << 10,
|
||||
NSURLBookmarkCreationOption::SecurityScoped => 1 << 11,
|
||||
NSURLBookmarkCreationOption::SecurityScopedReadOnly => 1 << 12
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Options used when resolving bookmark data.
|
||||
#[derive(Debug)]
|
||||
pub enum NSURLBookmarkResolutionOption {
|
||||
/// Specifies that no UI feedback should accompany resolution of the bookmark data.
|
||||
WithoutUI,
|
||||
|
||||
/// Specifies that no volume should be mounted during resolution of the bookmark data.
|
||||
WithoutMounting,
|
||||
|
||||
/// Specifies that the security scope, applied to the bookmark when it was created, should
|
||||
/// be used during resolution of the bookmark data.
|
||||
SecurityScoped
|
||||
}
|
||||
|
||||
impl From<NSURLBookmarkResolutionOption> for NSUInteger {
|
||||
fn from(flag: NSURLBookmarkResolutionOption) -> NSUInteger {
|
||||
match flag {
|
||||
NSURLBookmarkResolutionOption::WithoutUI => 1u64 << 8,
|
||||
NSURLBookmarkResolutionOption::WithoutMounting => 1u64 << 9,
|
||||
NSURLBookmarkResolutionOption::SecurityScoped => 1 << 10
|
||||
}
|
||||
}
|
||||
}
|
181
src/foundation/urls/mod.rs
Normal file
181
src/foundation/urls/mod.rs
Normal file
|
@ -0,0 +1,181 @@
|
|||
use std::error::Error;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc::runtime::Object;
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::{id, nil, NSData, NSString, NSUInteger};
|
||||
|
||||
mod bookmark_options;
|
||||
pub use bookmark_options::{NSURLBookmarkCreationOption, NSURLBookmarkResolutionOption};
|
||||
|
||||
mod resource_keys;
|
||||
pub use resource_keys::{NSURLResourceKey, NSURLFileResource, NSUbiquitousItemDownloadingStatus};
|
||||
|
||||
/// Wraps `NSURL` for use throughout the framework.
|
||||
///
|
||||
/// This type may also be returned to users in some callbacks (e.g, file manager/selectors) as it's
|
||||
/// a core part of the macOS/iOS experience and bridging around it is arguably blocking people from
|
||||
/// being able to actually build useful things.
|
||||
///
|
||||
/// For pure-Rust developers who have no interest in the Objective-C underpinnings, there's a
|
||||
/// `pathbuf()` method that returns an `std::path::PathBuf` for working with. Note that this will
|
||||
/// prove less useful in sandboxed applications, and if the underlying file that the PathBuf points
|
||||
/// to moves, you'll be responsible for figuring out exactly what you do there.
|
||||
///
|
||||
/// Otherwise, this struct bridges enough of NSURL to be useful (loading, using, and bookmarks).
|
||||
/// Pull requests for additional functionality are welcome.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NSURL<'a> {
|
||||
/// A reference to the backing `NSURL`.
|
||||
pub objc: ShareId<Object>,
|
||||
phantom: PhantomData<&'a ()>
|
||||
}
|
||||
|
||||
impl<'a> NSURL<'a> {
|
||||
/// In cases where we're vended an `NSURL` by the system, this can be used to wrap and
|
||||
/// retain it.
|
||||
pub fn retain(object: id) -> Self {
|
||||
NSURL {
|
||||
objc: unsafe { ShareId::from_ptr(object) },
|
||||
phantom: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// In some cases, we want to wrap a system-provided NSURL without retaining it.
|
||||
pub fn from_retained(object: id) -> Self {
|
||||
NSURL {
|
||||
objc: unsafe { ShareId::from_retained_ptr(object) },
|
||||
phantom: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates and returns a URL object by calling through to `[NSURL URLWithString]`.
|
||||
pub fn with_str(url: &str) -> Self {
|
||||
let url = NSString::new(url);
|
||||
|
||||
Self {
|
||||
objc: unsafe {
|
||||
ShareId::from_ptr(msg_send![class!(NSURL), URLWithString:&*url])
|
||||
},
|
||||
|
||||
phantom: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the absolute string path that this URL points to.
|
||||
///
|
||||
/// Note that if the underlying file moved, this won't be accurate - you likely want to
|
||||
/// research URL bookmarks.
|
||||
pub fn absolute_string(&self) -> String {
|
||||
let abs_str = NSString::retain(unsafe {
|
||||
msg_send![&*self.objc, absoluteString]
|
||||
});
|
||||
|
||||
abs_str.to_string()
|
||||
}
|
||||
|
||||
/// Creates and returns a Rust `PathBuf`, for users who don't need the extra pieces of NSURL
|
||||
/// and just want to write Rust code.
|
||||
pub fn pathbuf(&self) -> PathBuf {
|
||||
let path = NSString::retain(unsafe {
|
||||
msg_send![&*self.objc, path]
|
||||
});
|
||||
|
||||
path.to_str().into()
|
||||
}
|
||||
|
||||
/// Returns bookmark data for this URL. Will error if the underlying API errors.
|
||||
///
|
||||
/// Bookmarks are useful for sandboxed applications, as well as situations where you might want
|
||||
/// to later resolve the true location of a file (e.g, if the user moved it between when you
|
||||
/// got the URL and when you need to use it).
|
||||
pub fn bookmark_data(
|
||||
&self,
|
||||
options: &[NSURLBookmarkCreationOption],
|
||||
resource_value_keys: &[NSURLResourceKey],
|
||||
relative_to_url: Option<NSURL>
|
||||
) -> Result<NSData, Box<dyn Error>> {
|
||||
let mut opts: NSUInteger = 0;
|
||||
for mask in options {
|
||||
let i: NSUInteger = mask.into();
|
||||
opts = opts | i;
|
||||
}
|
||||
|
||||
// Build NSArray of resource keys
|
||||
let resource_keys = nil;
|
||||
|
||||
// Mutability woes mean we just go through a match here to satisfy message passing needs.
|
||||
let bookmark_data = NSData::retain(match relative_to_url {
|
||||
Some(relative_url) => unsafe {
|
||||
msg_send![&*self.objc, bookmarkDataWithOptions:opts
|
||||
includingResourceValuesForKeys:resource_keys
|
||||
relativeToURL:relative_url
|
||||
error:nil
|
||||
]
|
||||
},
|
||||
|
||||
None => unsafe {
|
||||
msg_send![&*self.objc, bookmarkDataWithOptions:opts
|
||||
includingResourceValuesForKeys:resource_keys
|
||||
relativeToURL:nil
|
||||
error:nil
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Check for errors...
|
||||
//Err("LOL".into())
|
||||
|
||||
Ok(bookmark_data)
|
||||
}
|
||||
|
||||
/// Converts bookmark data into a URL.
|
||||
pub fn from_bookmark_data(
|
||||
data: NSData,
|
||||
options: &[NSURLBookmarkResolutionOption],
|
||||
relative_to_url: Option<NSURL>,
|
||||
data_is_stale: bool
|
||||
) -> Result<Self, Box<dyn Error>> {
|
||||
Err("LOL".into())
|
||||
}
|
||||
|
||||
/// In an app that has adopted App Sandbox, makes the resource pointed to by a security-scoped URL available to the app.
|
||||
///
|
||||
/// More information can be found at:
|
||||
/// [https://developer.apple.com/documentation/foundation/nsurl/1417051-startaccessingsecurityscopedreso?language=objc]
|
||||
pub fn start_accessing_security_scoped_resource(&self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, startAccessingSecurityScopedResource];
|
||||
}
|
||||
}
|
||||
|
||||
/// In an app that adopts App Sandbox, revokes access to the resource pointed to by a security-scoped URL.
|
||||
///
|
||||
/// More information can be found at:
|
||||
/// [https://developer.apple.com/documentation/foundation/nsurl/1413736-stopaccessingsecurityscopedresou?language=objc]
|
||||
pub fn stop_accessing_security_scoped_resource(&self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.objc, stopAccessingSecurityScopedResource];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*impl From<NSString<'_>> for id {
|
||||
/// Consumes and returns the pointer to the underlying NSString instance.
|
||||
fn from(mut string: NSString) -> Self {
|
||||
&mut *string.objc
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Deref for NSURL<'_> {
|
||||
type Target = Object;
|
||||
|
||||
/// Derefs to the underlying Objective-C Object.
|
||||
fn deref(&self) -> &Object {
|
||||
&*self.objc
|
||||
}
|
||||
}
|
170
src/foundation/urls/resource_keys.rs
Normal file
170
src/foundation/urls/resource_keys.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use crate::foundation::id;
|
||||
|
||||
/// Possible values for the `NSURLResourceKey::FileResourceType` key.
|
||||
#[derive(Debug)]
|
||||
pub enum NSURLFileResource {
|
||||
/// The resource is a named pipe.
|
||||
NamedPipe,
|
||||
|
||||
/// The resource is a character special file.
|
||||
CharacterSpecial,
|
||||
|
||||
/// The resource is a directory.
|
||||
Directory,
|
||||
|
||||
/// The resource is a block special file.
|
||||
BlockSpecial,
|
||||
|
||||
/// The resource is a regular file.
|
||||
Regular,
|
||||
|
||||
/// The resource is a symbolic link.
|
||||
SymbolicLink,
|
||||
|
||||
/// The resource is a socket.
|
||||
Socket,
|
||||
|
||||
/// The resource’s type is unknown.
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// Values that describe the iCloud storage state of a file.
|
||||
#[derive(Debug)]
|
||||
pub enum NSUbiquitousItemDownloadingStatus {
|
||||
/// A local copy of this item exists and is the most up-to-date version known to the device.
|
||||
Current,
|
||||
|
||||
/// A local copy of this item exists, but it is stale. The most recent version will be downloaded as soon as possible.
|
||||
Downloaded,
|
||||
|
||||
/// This item has not been downloaded yet. Initiate a download.
|
||||
NotDownloaded
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NSURLResourceKey {
|
||||
IsApplication,
|
||||
IsScriptable,
|
||||
IsDirectory,
|
||||
ParentDirectoryURL,
|
||||
FileAllocatedSize,
|
||||
FileProtection,
|
||||
FileProtectionType,
|
||||
FileResourceIdentifier,
|
||||
FileResourceType(NSURLFileResource),
|
||||
FileSecurity,
|
||||
FileSize,
|
||||
IsAliasFile,
|
||||
IsPackage,
|
||||
IsRegularFile,
|
||||
PreferredIOBlockSize,
|
||||
TotalFileAllocatedSize,
|
||||
TotalFileSize,
|
||||
|
||||
VolumeAvailableCapacity,
|
||||
VolumeAvailableCapacityForImportantUsage,
|
||||
VolumeAvailableCapacityForOpportunisticUsage,
|
||||
VolumeTotalCapacity,
|
||||
VolumeIsAutomounted,
|
||||
VolumeIsBrowsable,
|
||||
VolumeIsEjectable,
|
||||
VolumeIsEncrypted,
|
||||
VolumeIsInternal,
|
||||
VolumeIsJournaling,
|
||||
VolumeIsLocal,
|
||||
VolumeIsReadOnly,
|
||||
VolumeIsRemovable,
|
||||
VolumeIsRootFileSystem,
|
||||
|
||||
IsMountTrigger,
|
||||
IsVolume,
|
||||
VolumeCreationDate,
|
||||
VolumeIdentifier,
|
||||
VolumeLocalizedFormatDescription,
|
||||
VolumeLocalizedName,
|
||||
VolumeMaximumFileSize,
|
||||
VolumeName,
|
||||
VolumeResourceCount,
|
||||
VolumeSupportsAccessPermissions,
|
||||
VolumeSupportsAdvisoryFileLocking,
|
||||
VolumeSupportsCasePreservedNames,
|
||||
VolumeSupportsCaseSensitiveNames,
|
||||
VolumeSupportsCompression,
|
||||
VolumeSupportsExclusiveRenaming,
|
||||
VolumeSupportsExtendedSecurity,
|
||||
VolumeSupportsFileCloning,
|
||||
VolumeSupportsHardLinks,
|
||||
VolumeSupportsImmutableFiles,
|
||||
VolumeSupportsJournaling,
|
||||
VolumeSupportsPersistentIDs,
|
||||
VolumeSupportsRenaming,
|
||||
VolumeSupportsRootDirectoryDates,
|
||||
VolumeSupportsSparseFiles,
|
||||
VolumeSupportsSwapRenaming,
|
||||
VolumeSupportsSymbolicLinks,
|
||||
VolumeSupportsVolumeSizes,
|
||||
VolumeSupportsZeroRuns,
|
||||
VolumeURLForRemounting,
|
||||
VolumeURL,
|
||||
VolumeUUIDString,
|
||||
|
||||
IsUbiquitousItem,
|
||||
UbiquitousSharedItemMostRecentEditorNameComponents,
|
||||
UbiquitousItemDownloadRequested,
|
||||
UbiquitousItemIsDownloading,
|
||||
UbiquitousItemDownloadingError,
|
||||
UbiquitousItemDownloadingStatus(NSUbiquitousItemDownloadingStatus),
|
||||
UbiquitousItemIsUploaded,
|
||||
UbiquitousItemIsUploading,
|
||||
UbiquitousItemUploadingError,
|
||||
UbiquitousItemHasUnresolvedConflicts,
|
||||
UbiquitousItemContainerDisplayName,
|
||||
UbiquitousSharedItemOwnerNameComponents,
|
||||
UbiquitousSharedItemCurrentUserPermissions,
|
||||
UbiquitousSharedItemCurrentUserRole,
|
||||
UbiquitousItemIsShared,
|
||||
UbiquitousSharedItemRole,
|
||||
UbiquitousSharedItemPermissions,
|
||||
|
||||
ThumbnailDictionaryItem,
|
||||
|
||||
KeysOfUnsetValues,
|
||||
QuarantineProperties,
|
||||
AddedToDirectoryDate,
|
||||
AttributeModificationDate,
|
||||
ContentAccessDate,
|
||||
ContentModificationDate,
|
||||
CreationDate,
|
||||
CustomIcon,
|
||||
DocumentIdentifier,
|
||||
EffectiveIcon,
|
||||
GenerationIdentifier,
|
||||
HasHiddenExtension,
|
||||
IsExcludedFromBackup,
|
||||
IsExecutable,
|
||||
IsHidden,
|
||||
IsReadable,
|
||||
IsSymbolicLink,
|
||||
IsSystemImmutable,
|
||||
IsUserImmutable,
|
||||
IsWritable,
|
||||
LabelColor,
|
||||
LabelNumber,
|
||||
LinkCount,
|
||||
LocalizedLabel,
|
||||
LocalizedName,
|
||||
LocalizedTypeDescription,
|
||||
Name,
|
||||
Path,
|
||||
CanonicalPath,
|
||||
TagNames,
|
||||
ContentType,
|
||||
|
||||
FileContentIdentifier,
|
||||
IsPurgeable,
|
||||
IsSparse,
|
||||
MayHaveExtendedAttributes,
|
||||
MayShareFileContent,
|
||||
UbiquitousItemIsExcludedFromSync,
|
||||
VolumeSupportsFileProtection
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
use crate::foundation::id;
|
||||
|
||||
/// These icons are system-provided icons that are guaranteed to exist in all versions of macOS
|
||||
/// that Cacao supports. These will use SFSymbols on Big Sur and onwards (11.0+), and the correct
|
||||
/// controls for prior macOS versions.
|
||||
|
@ -22,51 +24,86 @@ pub enum MacSystemIcon {
|
|||
|
||||
/// Returns a stock "+" icon that's common to the system. Use this for buttons that need the
|
||||
/// symbol.
|
||||
Add
|
||||
Add,
|
||||
|
||||
/// A stock "-" icon that's common to the system. Use this for buttons that need the symbol.
|
||||
Remove,
|
||||
|
||||
/// Returns a Folder icon.
|
||||
Folder
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
static NSImageNamePreferencesGeneral: id;
|
||||
static NSImageNameAdvanced: id;
|
||||
static NSImageNameUserAccounts: id;
|
||||
static NSImageNameAddTemplate: id;
|
||||
static NSImageNameFolder: id;
|
||||
static NSImageNameRemoveTemplate: id;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl MacSystemIcon {
|
||||
/// Maps system icons to their pre-11.0 framework identifiers.
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
MacSystemIcon::PreferencesGeneral => "NSPreferencesGeneral",
|
||||
MacSystemIcon::PreferencesAdvanced => "NSAdvanced",
|
||||
MacSystemIcon::PreferencesUserAccounts => "NSUserAccounts",
|
||||
MacSystemIcon::Add => "NSImageNameAddTemplate"
|
||||
pub fn to_id(&self) -> id {
|
||||
unsafe {
|
||||
match self {
|
||||
MacSystemIcon::PreferencesGeneral => NSImageNamePreferencesGeneral,
|
||||
MacSystemIcon::PreferencesAdvanced => NSImageNameAdvanced,
|
||||
MacSystemIcon::PreferencesUserAccounts => NSImageNameUserAccounts,
|
||||
MacSystemIcon::Add => NSImageNameAddTemplate,
|
||||
MacSystemIcon::Remove => NSImageNameRemoveTemplate,
|
||||
MacSystemIcon::Folder => NSImageNameFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps system icons to their SFSymbols-counterparts for use on 11.0+.
|
||||
pub fn to_sfsymbol_str(&self) -> &'static str {
|
||||
match self {
|
||||
MacSystemIcon::PreferencesGeneral => "gearshape",
|
||||
MacSystemIcon::PreferencesAdvanced => "slider.vertical.3",
|
||||
MacSystemIcon::PreferencesUserAccounts => "at",
|
||||
MacSystemIcon::Add => "plus"
|
||||
MacSystemIcon::PreferencesGeneral => SFSymbol::GearShape.to_str(),
|
||||
MacSystemIcon::PreferencesAdvanced => SFSymbol::SliderVertical3.to_str(),
|
||||
MacSystemIcon::PreferencesUserAccounts => SFSymbol::AtSymbol.to_str(),
|
||||
MacSystemIcon::Add => SFSymbol::Plus.to_str(),
|
||||
MacSystemIcon::Remove => SFSymbol::Minus.to_str(),
|
||||
MacSystemIcon::Folder => SFSymbol::FolderFilled.to_str()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SFSymbol {
|
||||
AtSymbol,
|
||||
GearShape,
|
||||
FolderFilled,
|
||||
PaperPlane,
|
||||
PaperPlaneFilled,
|
||||
Plus,
|
||||
Minus,
|
||||
SliderVertical3,
|
||||
SquareAndArrowUpOnSquare,
|
||||
SquareAndArrowUpOnSquareFill,
|
||||
SquareAndArrowDownOnSquare,
|
||||
SquareAndArrowDownOnSquareFill
|
||||
SquareAndArrowDownOnSquareFill,
|
||||
SquareDashed
|
||||
}
|
||||
|
||||
impl SFSymbol {
|
||||
pub fn to_str(&self) -> &str {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
Self::AtSymbol => "at",
|
||||
Self::GearShape => "gearshape",
|
||||
Self::FolderFilled => "folder.fill",
|
||||
Self::PaperPlane => "paperplane",
|
||||
Self::PaperPlaneFilled => "paperplane.fill",
|
||||
Self::Plus => "plus",
|
||||
Self::Minus => "minus",
|
||||
Self::SliderVertical3 => "slider.vertical.3",
|
||||
Self::SquareAndArrowUpOnSquare => "square.and.arrow.up.on.square",
|
||||
Self::SquareAndArrowUpOnSquareFill => "square.and.arrow.up.on.square.fill",
|
||||
Self::SquareAndArrowDownOnSquare => "square.and.arrow.down.on.square",
|
||||
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill"
|
||||
Self::SquareAndArrowDownOnSquareFill => "square.and.arrow.down.on.square.fill",
|
||||
Self::SquareDashed => "square.dashed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use core_graphics::{
|
|||
};
|
||||
use core_graphics::context::{CGContext, CGContextRef};
|
||||
|
||||
use crate::foundation::{id, YES, NO, NSString};
|
||||
use crate::foundation::{id, YES, NO, NSString, NSData};
|
||||
use crate::utils::os;
|
||||
use super::icons::*;
|
||||
|
||||
|
@ -131,12 +131,53 @@ impl Image {
|
|||
})
|
||||
}
|
||||
|
||||
/// Loads an image from the specified path.
|
||||
pub fn with_contents_of_file(path: &str) -> Self {
|
||||
let file_path = NSString::new(path);
|
||||
|
||||
Image(unsafe {
|
||||
let alloc: id = msg_send![class!(NSImage), alloc];
|
||||
ShareId::from_ptr(msg_send![alloc, initWithContentsOfFile:file_path])
|
||||
})
|
||||
}
|
||||
|
||||
/// Given a Vec of data, will transform it into an Image by passing it through NSData.
|
||||
/// This can be useful for when you need to include_bytes!() something into your binary.
|
||||
pub fn with_data(data: &[u8]) -> Self {
|
||||
let data = NSData::with_slice(data);
|
||||
|
||||
Image(unsafe {
|
||||
let alloc: id = msg_send![class!(NSImage), alloc];
|
||||
ShareId::from_ptr(msg_send![alloc, initWithData:data])
|
||||
})
|
||||
}
|
||||
|
||||
// @TODO: for Airyx, unsure if this is supported - and it's somewhat modern macOS-specific, so
|
||||
// let's keep the os flag here for now.
|
||||
/// Returns a stock system icon. These are guaranteed to exist across all versions of macOS
|
||||
/// supported.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn system_icon(icon: MacSystemIcon, accessibility_description: &str) -> Self {
|
||||
pub fn system_icon(icon: MacSystemIcon) -> Self {
|
||||
Image(unsafe {
|
||||
ShareId::from_ptr({
|
||||
let icon = icon.to_id();
|
||||
msg_send![class!(NSImage), imageNamed:icon]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// @TODO: for Airyx, unsure if this is supported - and it's somewhat modern macOS-specific, so
|
||||
// let's keep the os flag here for now.
|
||||
/// The name here can be confusing, I know.
|
||||
///
|
||||
/// A system symbol will swap an SFSymbol in for macOS 11.0+, but return the correct
|
||||
/// MacSystemIcon image type for versions prior to that. This is mostly helpful in situations
|
||||
/// like Preferences windows, where you want to have the correct modern styling for newer OS
|
||||
/// versions.
|
||||
///
|
||||
/// However, if you need the correct "folder" icon for instance, you probably want `system_icon`.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn system_symbol(icon: MacSystemIcon, accessibility_description: &str) -> Self {
|
||||
Image(unsafe {
|
||||
ShareId::from_ptr(match os::is_minimum_version(11) {
|
||||
true => {
|
||||
|
@ -147,8 +188,8 @@ impl Image {
|
|||
},
|
||||
|
||||
false => {
|
||||
let icon = NSString::new(icon.to_str());
|
||||
msg_send![class!(NSImage), imageNamed:&*icon]
|
||||
let icon = icon.to_id();
|
||||
msg_send![class!(NSImage), imageNamed:icon]
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@ use objc::{msg_send, sel, sel_impl};
|
|||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
||||
use crate::color::Color;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
|
@ -147,23 +148,34 @@ impl ImageView {
|
|||
});
|
||||
}
|
||||
|
||||
/// Given an image reference, sets it on the image view. You're currently responsible for
|
||||
/// retaining this yourself.
|
||||
pub fn set_image(&self, image: &Image) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setImage:&*image.0];
|
||||
});
|
||||
}
|
||||
|
||||
/*pub fn set_image_scaling(&self, scaling_type: ImageScale) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
|
||||
let _: () = msg_send![obj, setImageScaling:
|
||||
});
|
||||
}*/
|
||||
}
|
||||
|
||||
impl Layout for ImageView {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl ObjcAccess for ImageView {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ImageView {}
|
||||
|
||||
impl Drop for ImageView {
|
||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||
|
|
|
@ -45,8 +45,10 @@ use objc::{msg_send, sel, sel_impl};
|
|||
use objc_id::ShareId;
|
||||
|
||||
use crate::color::Color;
|
||||
use crate::control::Control;
|
||||
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES};
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::text::{Font, TextAlign};
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
|
@ -309,6 +311,15 @@ impl<T> TextField<T> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Call this to set the text for the label.
|
||||
pub fn set_placeholder_text(&self, text: &str) {
|
||||
let s = NSString::new(text);
|
||||
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setPlaceholderString:&*s];
|
||||
});
|
||||
}
|
||||
|
||||
/// The the text alignment style for this control.
|
||||
pub fn set_text_alignment(&self, alignment: TextAlign) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
|
@ -317,6 +328,35 @@ impl<T> TextField<T> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Set whether this field operates in single-line mode.
|
||||
pub fn set_uses_single_line(&self, uses_single_line: bool) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let cell: id = msg_send![obj, cell];
|
||||
let _: () = msg_send![cell, setUsesSingleLineMode:match uses_single_line {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Set whether this field operates in single-line mode.
|
||||
pub fn set_wraps(&self, uses_single_line: bool) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let cell: id = msg_send![obj, cell];
|
||||
let _: () = msg_send![cell, setWraps:match uses_single_line {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the maximum number of lines.
|
||||
pub fn set_max_number_of_lines(&self, num: NSInteger) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setMaximumNumberOfLines:num];
|
||||
});
|
||||
}
|
||||
|
||||
/// Sets the font for this input.
|
||||
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
|
||||
let font = font.as_ref().clone();
|
||||
|
@ -327,16 +367,20 @@ impl<T> TextField<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for TextField<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for TextField<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for TextField<T> {}
|
||||
|
||||
impl<T> Control for TextField<T> {}
|
||||
|
||||
impl<T> Drop for TextField<T> {
|
||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `TextField` is being
|
||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||
|
|
18
src/keys.rs
Normal file
18
src/keys.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! This provides some basic mapping for providing Key characters to controls. It's mostly meant as
|
||||
//! a wrapper to stop magic symbols all over the place.
|
||||
|
||||
/// Represents a Key character.
|
||||
#[derive(Debug)]
|
||||
pub enum Key<'a> {
|
||||
/// Behind the scenes, this translates to NSDeleteCharacter (for AppKit).
|
||||
Delete,
|
||||
|
||||
/// Whatever character you want.
|
||||
Char(&'a str)
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Key<'a> {
|
||||
fn from(s: &'a str) -> Self {
|
||||
Key::Char(s)
|
||||
}
|
||||
}
|
27
src/layout/animator.rs
Normal file
27
src/layout/animator.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use core_graphics::base::CGFloat;
|
||||
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use objc::runtime::{Class, Object};
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::id;
|
||||
|
||||
/// A wrapper for an animation proxy object in Cocoa that supports basic animations.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LayoutConstraintAnimatorProxy(pub ShareId<Object>);
|
||||
|
||||
impl LayoutConstraintAnimatorProxy {
|
||||
/// Wraps and returns a proxy for animation of layout constraint values.
|
||||
pub fn new(proxy: id) -> Self {
|
||||
Self(unsafe {
|
||||
ShareId::from_ptr(msg_send![proxy, animator])
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the constant (usually referred to as `offset` in Cacao) value for the constraint being animated.
|
||||
pub fn set_offset(&self, value: CGFloat) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.0, setConstant:value];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ use objc_id::ShareId;
|
|||
|
||||
use crate::foundation::{id, YES, NO};
|
||||
|
||||
use super::LayoutConstraintAnimatorProxy;
|
||||
|
||||
/// A wrapper for `NSLayoutConstraint`. This both acts as a central path through which to activate
|
||||
/// constraints, as well as a wrapper for layout constraints that are not axis bound (e.g, width or
|
||||
/// height).
|
||||
|
@ -27,16 +29,20 @@ pub struct LayoutConstraint {
|
|||
|
||||
/// The priority used in computing this constraint.
|
||||
pub priority: f64,
|
||||
|
||||
/// An animator proxy that can be used inside animation contexts.
|
||||
pub animator: LayoutConstraintAnimatorProxy
|
||||
}
|
||||
|
||||
impl LayoutConstraint {
|
||||
/// An internal method for wrapping existing constraints.
|
||||
pub(crate) fn new(object: id) -> Self {
|
||||
LayoutConstraint {
|
||||
animator: LayoutConstraintAnimatorProxy::new(object),
|
||||
constraint: unsafe { ShareId::from_ptr(object) },
|
||||
offset: 0.0,
|
||||
multiplier: 0.0,
|
||||
priority: 0.0
|
||||
priority: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,6 +56,7 @@ impl LayoutConstraint {
|
|||
|
||||
|
||||
LayoutConstraint {
|
||||
animator: self.animator,
|
||||
constraint: self.constraint,
|
||||
offset: offset,
|
||||
multiplier: self.multiplier,
|
||||
|
@ -57,6 +64,7 @@ impl LayoutConstraint {
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the offset of a borrowed constraint.
|
||||
pub fn set_offset<F: Into<f64>>(&self, offset: F) {
|
||||
let offset: f64 = offset.into();
|
||||
|
||||
|
|
|
@ -3,35 +3,44 @@
|
|||
//! `AutoLayout` feature, each widget will default to using AutoLayout, which can be beneficial in
|
||||
//! more complicated views that need to deal with differing screen sizes.
|
||||
|
||||
pub mod traits;
|
||||
mod traits;
|
||||
pub use traits::Layout;
|
||||
|
||||
mod animator;
|
||||
pub use animator::LayoutConstraintAnimatorProxy;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub mod attributes;
|
||||
mod attributes;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use attributes::*;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub mod constraint;
|
||||
mod constraint;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use constraint::LayoutConstraint;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub mod dimension;
|
||||
mod dimension;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use dimension::LayoutAnchorDimension;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub mod horizontal;
|
||||
mod horizontal;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use horizontal::LayoutAnchorX;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub mod vertical;
|
||||
mod vertical;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use vertical::LayoutAnchorY;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
mod safe_guide;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub use safe_guide::SafeAreaLayoutGuide;
|
||||
|
|
67
src/layout/safe_guide.rs
Normal file
67
src/layout/safe_guide.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
use crate::foundation::id;
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::utils::os;
|
||||
|
||||
/// A SafeAreaLayoutGuide should exist on all view types, and ensures that there are anchor points
|
||||
/// that work within the system constraints. On macOS 11+, this will ensure you work around system
|
||||
/// padding transprently - on macOS 10.15 and under, this will transparently map to the normal
|
||||
/// edges, as the underlying properties were not supported there.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SafeAreaLayoutGuide {
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
pub top: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||
pub leading: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime left layout constraint.
|
||||
pub left: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||
pub trailing: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime right layout constraint.
|
||||
pub right: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||
pub bottom: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime width layout constraint.
|
||||
pub width: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime height layout constraint.
|
||||
pub height: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||
pub center_x: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||
pub center_y: LayoutAnchorY
|
||||
}
|
||||
|
||||
impl SafeAreaLayoutGuide {
|
||||
/// Given a view pointer, will extract the safe area layout guide properties and return a
|
||||
/// `SafeAreaLayoutGuide` composed of them.
|
||||
pub fn new(view: id) -> Self {
|
||||
// For versions prior to Big Sur, we'll just use the default view anchors in place.
|
||||
let guide: id = match os::is_minimum_version(11) {
|
||||
true => unsafe { msg_send![view, layoutMarginsGuide] },
|
||||
false => view
|
||||
};
|
||||
|
||||
Self {
|
||||
top: LayoutAnchorY::top(guide),
|
||||
left: LayoutAnchorX::left(guide),
|
||||
leading: LayoutAnchorX::leading(guide),
|
||||
right: LayoutAnchorX::right(guide),
|
||||
trailing: LayoutAnchorX::trailing(guide),
|
||||
bottom: LayoutAnchorY::bottom(guide),
|
||||
width: LayoutAnchorDimension::width(guide),
|
||||
height: LayoutAnchorDimension::height(guide),
|
||||
center_x: LayoutAnchorX::center(guide),
|
||||
center_y: LayoutAnchorY::center(guide)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
//! Various traits related to controllers opting in to autolayout routines and support for view
|
||||
//! heirarchies.
|
||||
|
||||
use core_graphics::base::CGFloat;
|
||||
use core_graphics::geometry::{CGRect, CGPoint, CGSize};
|
||||
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
@ -9,27 +10,21 @@ use objc_id::ShareId;
|
|||
|
||||
use crate::foundation::{id, nil, to_bool, YES, NO, NSArray, NSString};
|
||||
use crate::geometry::Rect;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
use crate::pasteboard::PasteboardType;
|
||||
|
||||
/// A trait that view wrappers must conform to. Enables managing the subview tree.
|
||||
#[allow(unused_variables)]
|
||||
pub trait Layout {
|
||||
/// Used for mutably interacting with the underlying Objective-C instance.
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F);
|
||||
|
||||
/// Used for checking backing properties of the underlying Objective-C instance, without
|
||||
/// needing a mutable borrow.
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R;
|
||||
|
||||
pub trait Layout: ObjcAccess {
|
||||
/// Sets whether this needs to be redrawn before being displayed.
|
||||
///
|
||||
/// If you're updating data that dynamically impacts this view, mark this as true - the next
|
||||
/// pass from the system will redraw it accordingly, and set the underlying value back to
|
||||
/// `false`.
|
||||
fn set_needs_display(&self, needs_display: bool) {
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setNeedsDisplay:match needs_display {
|
||||
true => YES,
|
||||
false => NO
|
||||
|
@ -39,8 +34,8 @@ pub trait Layout {
|
|||
|
||||
/// Adds another Layout-backed control or view as a subview of this view.
|
||||
fn add_subview<V: Layout>(&self, view: &V) {
|
||||
self.with_backing_node(|backing_node| {
|
||||
view.with_backing_node(|subview_node| unsafe {
|
||||
self.with_backing_obj_mut(|backing_node| {
|
||||
view.with_backing_obj_mut(|subview_node| unsafe {
|
||||
let _: () = msg_send![backing_node, addSubview:subview_node];
|
||||
});
|
||||
});
|
||||
|
@ -48,7 +43,7 @@ pub trait Layout {
|
|||
|
||||
/// Removes a control or view from the superview.
|
||||
fn remove_from_superview(&self) {
|
||||
self.with_backing_node(|backing_node| unsafe {
|
||||
self.with_backing_obj_mut(|backing_node| unsafe {
|
||||
let _: () = msg_send![backing_node, removeFromSuperview];
|
||||
});
|
||||
}
|
||||
|
@ -61,7 +56,7 @@ pub trait Layout {
|
|||
fn set_frame<R: Into<CGRect>>(&self, rect: R) {
|
||||
let frame: CGRect = rect.into();
|
||||
|
||||
self.with_backing_node(move |backing_node| unsafe {
|
||||
self.with_backing_obj_mut(move |backing_node| unsafe {
|
||||
let _: () = msg_send![backing_node, setFrame:frame];
|
||||
});
|
||||
}
|
||||
|
@ -73,7 +68,7 @@ pub trait Layout {
|
|||
/// then you should set this to `true` (or use an appropriate initializer that does it for you).
|
||||
#[cfg(feature = "autolayout")]
|
||||
fn set_translates_autoresizing_mask_into_constraints(&self, translates: bool) {
|
||||
self.with_backing_node(|backing_node| unsafe {
|
||||
self.with_backing_obj_mut(|backing_node| unsafe {
|
||||
let _: () = msg_send![backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
|
||||
true => YES,
|
||||
false => NO
|
||||
|
@ -85,7 +80,7 @@ pub trait Layout {
|
|||
///
|
||||
/// When hidden, widgets don't receive events and is not visible.
|
||||
fn set_hidden(&self, hide: bool) {
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setHidden:match hide {
|
||||
true => YES,
|
||||
false => NO
|
||||
|
@ -98,7 +93,7 @@ pub trait Layout {
|
|||
/// Note that this can report `false` if an ancestor widget is hidden, thus hiding this - to check in
|
||||
/// that case, you may want `is_hidden_or_ancestor_is_hidden()`.
|
||||
fn is_hidden(&self) -> bool {
|
||||
self.get_from_backing_node(|obj| {
|
||||
self.get_from_backing_obj(|obj| {
|
||||
to_bool(unsafe {
|
||||
msg_send![obj, isHidden]
|
||||
})
|
||||
|
@ -108,7 +103,7 @@ pub trait Layout {
|
|||
/// Returns whether this is hidden, *or* whether an ancestor view is hidden.
|
||||
#[cfg(feature = "appkit")]
|
||||
fn is_hidden_or_ancestor_is_hidden(&self) -> bool {
|
||||
self.get_from_backing_node(|obj| {
|
||||
self.get_from_backing_obj(|obj| {
|
||||
to_bool(unsafe {
|
||||
msg_send![obj, isHiddenOrHasHiddenAncestor]
|
||||
})
|
||||
|
@ -126,7 +121,7 @@ pub trait Layout {
|
|||
x.into()
|
||||
}).collect::<Vec<id>>().into();
|
||||
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
|
||||
});
|
||||
}
|
||||
|
@ -137,7 +132,7 @@ pub trait Layout {
|
|||
/// currently to avoid compile issues.
|
||||
#[cfg(feature = "appkit")]
|
||||
fn unregister_dragged_types(&self) {
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, unregisterDraggedTypes];
|
||||
});
|
||||
}
|
||||
|
@ -148,7 +143,7 @@ pub trait Layout {
|
|||
/// can be helpful - but always test!
|
||||
#[cfg(feature = "appkit")]
|
||||
fn set_posts_frame_change_notifications(&self, posts: bool) {
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setPostsFrameChangedNotifications:match posts {
|
||||
true => YES,
|
||||
false => NO
|
||||
|
@ -162,11 +157,22 @@ pub trait Layout {
|
|||
/// can be helpful - but always test!
|
||||
#[cfg(feature = "appkit")]
|
||||
fn set_posts_bounds_change_notifications(&self, posts: bool) {
|
||||
self.with_backing_node(|obj| unsafe {
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setPostsBoundsChangedNotifications:match posts {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Theoretically this belongs elsewhere, but we want to enable this on all view layers, since
|
||||
/// it's common enough anyway.
|
||||
#[cfg(feature = "appkit")]
|
||||
fn set_alpha(&self, value: f64) {
|
||||
let value: CGFloat = value.into();
|
||||
|
||||
self.with_backing_obj_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setAlphaValue:value];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
13
src/lib.rs
13
src/lib.rs
|
@ -104,6 +104,8 @@ pub use lazy_static;
|
|||
#[cfg_attr(docsrs, doc(cfg(feature = "appkit")))]
|
||||
pub mod appkit;
|
||||
|
||||
//pub mod bundle;
|
||||
|
||||
#[cfg(feature = "uikit")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "uikit")))]
|
||||
pub mod uikit;
|
||||
|
@ -117,6 +119,9 @@ pub mod cloudkit;
|
|||
|
||||
pub mod color;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod control;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod dragdrop;
|
||||
|
||||
|
@ -138,14 +143,17 @@ pub mod image;
|
|||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod input;
|
||||
pub(crate) mod invoker;
|
||||
|
||||
pub mod keys;
|
||||
|
||||
pub mod layer;
|
||||
pub(crate) mod invoker;
|
||||
pub mod layout;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod listview;
|
||||
pub mod networking;
|
||||
pub(crate) mod objc_access;
|
||||
pub mod notification_center;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
|
@ -160,6 +168,9 @@ pub mod scrollview;
|
|||
#[cfg(feature = "appkit")]
|
||||
pub mod switch;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod select;
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
pub mod text;
|
||||
|
||||
|
|
|
@ -37,9 +37,16 @@ extern fn view_for_column<T: ListViewDelegate>(
|
|||
this: &Object,
|
||||
_: Sel,
|
||||
_table_view: id,
|
||||
_: id,
|
||||
_table_column: id,
|
||||
item: NSInteger
|
||||
) -> id {
|
||||
/*use core_graphics::geometry::CGRect;
|
||||
unsafe {
|
||||
//let superview: id = msg_send![table_view, superview];
|
||||
let frame: CGRect = msg_send![table_view, frame];
|
||||
let _: () = msg_send![table_column, setWidth:frame.size.width];
|
||||
}*/
|
||||
|
||||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||
let item = view.item_for(item as usize);
|
||||
|
||||
|
@ -79,7 +86,7 @@ extern fn menu_needs_update<T: ListViewDelegate>(
|
|||
let _ = Menu::append(menu, items);
|
||||
}
|
||||
|
||||
/// NSTableView requires listening to an observer to detect row selection changes, but that is...
|
||||
/*/// NSTableView requires listening to an observer to detect row selection changes, but that is...
|
||||
/// even clunkier than what we do in this framework.
|
||||
///
|
||||
/// The other less obvious way is to subclass and override the `shouldSelectRow:` method; here, we
|
||||
|
@ -94,6 +101,24 @@ extern fn select_row<T: ListViewDelegate>(
|
|||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||
view.item_selected(item as usize);
|
||||
YES
|
||||
}*/
|
||||
|
||||
extern fn selection_did_change<T: ListViewDelegate>(
|
||||
this: &Object,
|
||||
_: Sel,
|
||||
notification: id
|
||||
) {
|
||||
let selected_row: NSInteger = unsafe {
|
||||
let tableview: id = msg_send![notification, object];
|
||||
msg_send![tableview, selectedRow]
|
||||
};
|
||||
|
||||
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
|
||||
if selected_row == -1 {
|
||||
view.item_selected(None);
|
||||
} else {
|
||||
view.item_selected(Some(selected_row as usize));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn row_actions_for_row<T: ListViewDelegate>(
|
||||
|
@ -203,7 +228,7 @@ pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>(instanc
|
|||
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, id) -> NSInteger);
|
||||
decl.add_method(sel!(tableView:willDisplayCell:forTableColumn:row:), will_display_cell::<T> as extern fn(&Object, _, id, id, id, NSInteger));
|
||||
decl.add_method(sel!(tableView:viewForTableColumn:row:), view_for_column::<T> as extern fn(&Object, _, id, id, NSInteger) -> id);
|
||||
decl.add_method(sel!(tableView:shouldSelectRow:), select_row::<T> as extern fn(&Object, _, id, NSInteger) -> BOOL);
|
||||
decl.add_method(sel!(tableViewSelectionDidChange:), selection_did_change::<T> as extern fn(&Object, _, id));
|
||||
decl.add_method(sel!(tableView:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
|
||||
|
||||
// A slot for some menu handling; we just let it be done here for now rather than do the
|
||||
|
|
|
@ -50,16 +50,16 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
|
||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger};
|
||||
use crate::color::Color;
|
||||
|
||||
use crate::layout::Layout;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::scrollview::ScrollView;
|
||||
use crate::utils::{os, CellFactory, CGSize};
|
||||
use crate::utils::properties::{ObjcProperty, PropertyNullable};
|
||||
use crate::view::ViewDelegate;
|
||||
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
use crate::appkit::menu::MenuItem;
|
||||
|
@ -99,11 +99,10 @@ use std::cell::RefCell;
|
|||
/// A helper method for instantiating view classes and applying default settings to them.
|
||||
fn common_init(class: *const Class) -> id {
|
||||
unsafe {
|
||||
// Note: we do *not* enable AutoLayout here as we're by default placing this in a scroll
|
||||
// view, and we want it to just do its thing.
|
||||
let tableview: id = msg_send![class, new];
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
let _: () = msg_send![tableview, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
// Let's... make NSTableView into UITableView-ish.
|
||||
#[cfg(feature = "appkit")]
|
||||
{
|
||||
|
@ -144,6 +143,9 @@ pub struct ListView<T = ()> {
|
|||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ObjcProperty,
|
||||
|
||||
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||
pub animator: ViewAnimatorProxy,
|
||||
|
||||
/// In AppKit, we need to manage the NSScrollView ourselves. It's a bit
|
||||
/// more old school like that...
|
||||
///
|
||||
|
@ -262,6 +264,11 @@ impl ListView {
|
|||
#[cfg(feature = "autolayout")]
|
||||
center_y: LayoutAnchorY::center(anchor_view),
|
||||
|
||||
// Note that AppKit needs this to be the ScrollView!
|
||||
// @TODO: Figure out if there's a use case for exposing the inner tableview animator
|
||||
// property...
|
||||
animator: ViewAnimatorProxy::new(anchor_view),
|
||||
|
||||
objc: ObjcProperty::retain(view),
|
||||
|
||||
scrollview
|
||||
|
@ -310,6 +317,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
|
|||
menu: PropertyNullable::default(),
|
||||
delegate: None,
|
||||
objc: ObjcProperty::retain(view),
|
||||
animator: ViewAnimatorProxy::new(anchor_view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(anchor_view),
|
||||
|
@ -355,12 +363,13 @@ impl<T> ListView<T> {
|
|||
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
|
||||
/// reference, customize and use the view but without the trickery of holding pieces of the
|
||||
/// delegate - the `View` is the only true holder of those.
|
||||
pub(crate) fn clone_as_handle(&self) -> ListView {
|
||||
pub fn clone_as_handle(&self) -> ListView {
|
||||
ListView {
|
||||
cell_factory: CellFactory::new(),
|
||||
menu: self.menu.clone(),
|
||||
delegate: None,
|
||||
objc: self.objc.clone(),
|
||||
animator: self.animator.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: self.top.clone(),
|
||||
|
@ -490,6 +499,34 @@ impl<T> ListView<T> {
|
|||
}
|
||||
}
|
||||
|
||||
/// This hack exists to avoid a bug with how Rust's model isn't really friendly with more
|
||||
/// old-school GUI models. The tl;dr is that we unfortunately have to cheat a bit to gracefully
|
||||
/// handle two conditions.
|
||||
///
|
||||
/// The gist of it is that there are two situations (`perform_batch_updates` and `insert_rows`)
|
||||
/// where we call over to the list view to, well, perform updates. This causes the internal
|
||||
/// machinery of AppKit to call to the delegate, and the delegate then - rightfully - calls to
|
||||
/// dequeue a cell.
|
||||
///
|
||||
/// The problem is then that dequeue'ing a cell requires borrowing the underlying cell handler,
|
||||
/// per Rust's model. We haven't been able to drop our existing lock though! Thus it winds up
|
||||
/// panic'ing and all hell breaks loose.
|
||||
///
|
||||
/// For now, we just drop to Objective-C and message pass directly to avoid a
|
||||
/// double-locking-attempt on the Rust side of things. This is explicitly not ideal, and if
|
||||
/// you're reading this and rightfully going "WTF?", I encourage you to contribute a solution
|
||||
/// if you can come up with one.
|
||||
///
|
||||
/// In practice, this hack isn't that bad - at least, no worse than existing Objective-C code.
|
||||
/// The behavior is relatively well understood and documented in the above paragraph, so I'm
|
||||
/// comfortable with the hack for now.
|
||||
///
|
||||
/// To be ultra-clear: the hack is that we don't `borrow_mut` before sending a message. It just
|
||||
/// feels dirty, hence the novel. ;P
|
||||
fn hack_avoid_dequeue_loop<F: Fn(&Object)>(&self, handler: F) {
|
||||
self.objc.get(handler);
|
||||
}
|
||||
|
||||
/// 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`,
|
||||
|
@ -511,14 +548,9 @@ impl<T> ListView<T> {
|
|||
let handle = self.clone_as_handle();
|
||||
update(handle);
|
||||
|
||||
// This is cheating, but there's no good way around it at the moment. If we (mutably) lock in
|
||||
// Rust here, firing this call will loop back around into `dequeue`, which will then
|
||||
// hit a double lock.
|
||||
//
|
||||
// Personally, I can live with this - `endUpdates` is effectively just flushing the
|
||||
// already added updates, so with this small hack here we're able to keep the mutable
|
||||
// borrow structure everywhere else, which feels "correct".
|
||||
self.objc.get(|obj| unsafe {
|
||||
// This is done for a very explicit reason; see the comments on the method itself for
|
||||
// an explanation.
|
||||
self.hack_avoid_dequeue_loop(|obj| unsafe {
|
||||
let _: () = msg_send![obj, endUpdates];
|
||||
});
|
||||
}
|
||||
|
@ -545,7 +577,9 @@ impl<T> ListView<T> {
|
|||
// has also retained it.
|
||||
let x = ShareId::from_ptr(index_set);
|
||||
|
||||
self.objc.with_mut(|obj| {
|
||||
// This is done for a very explicit reason; see the comments on the method itself for
|
||||
// an explanation.
|
||||
self.hack_avoid_dequeue_loop(|obj| {
|
||||
let _: () = msg_send![obj, insertRowsAtIndexes:&*x withAnimation:animation_options];
|
||||
});
|
||||
}
|
||||
|
@ -647,6 +681,15 @@ impl<T> ListView<T> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Makes this table view the first responder.
|
||||
#[cfg(feature = "appkit")]
|
||||
pub fn make_first_responder(&self) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let window: id = msg_send![&*obj, window];
|
||||
let _: () = msg_send![window, makeFirstResponder:&*obj];
|
||||
});
|
||||
}
|
||||
|
||||
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
|
||||
/// calls yourself, but often easier to implement.
|
||||
///
|
||||
|
@ -689,15 +732,15 @@ impl<T> ListView<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ListView<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for ListView<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
// In AppKit, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
||||
// what to do normally.
|
||||
#[cfg(feature = "appkit")]
|
||||
self.scrollview.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
// In AppKit, we need to provide the scrollview for layout purposes - iOS and tvOS will know
|
||||
// what to do normally.
|
||||
//
|
||||
|
@ -708,6 +751,8 @@ impl<T> Layout for ListView<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ListView<T> {}
|
||||
|
||||
impl<T> Drop for ListView<T> {
|
||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||
|
|
|
@ -52,11 +52,12 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
|||
use crate::color::Color;
|
||||
use crate::layer::Layer;
|
||||
use crate::layout::Layout;
|
||||
use crate::view::ViewDelegate;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::view::{ViewAnimatorProxy, ViewDelegate};
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension, SafeAreaLayoutGuide};
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
mod appkit;
|
||||
|
@ -93,12 +94,19 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
|||
/// side anyway.
|
||||
#[derive(Debug)]
|
||||
pub struct ListViewRow<T = ()> {
|
||||
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||
pub animator: ViewAnimatorProxy,
|
||||
|
||||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ObjcProperty,
|
||||
|
||||
/// A pointer to the delegate for this view.
|
||||
pub delegate: Option<Box<T>>,
|
||||
|
||||
/// A safe layout guide property.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub safe_layout_guide: SafeAreaLayoutGuide,
|
||||
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub top: LayoutAnchorY,
|
||||
|
@ -154,6 +162,10 @@ impl ListViewRow {
|
|||
ListViewRow {
|
||||
delegate: None,
|
||||
objc: ObjcProperty::retain(view),
|
||||
animator: ViewAnimatorProxy::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
@ -211,6 +223,10 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
let view = ListViewRow {
|
||||
delegate: Some(delegate),
|
||||
objc: ObjcProperty::retain(view),
|
||||
animator: ViewAnimatorProxy::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
@ -263,6 +279,10 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
let mut view = ListViewRow {
|
||||
delegate: None,
|
||||
objc: ObjcProperty::retain(view),
|
||||
animator: ViewAnimatorProxy::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
@ -311,6 +331,10 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
|
|||
ListViewRow {
|
||||
delegate: None,
|
||||
objc: self.objc.clone(),
|
||||
animator: self.animator.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: self.safe_layout_guide.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: self.top.clone(),
|
||||
|
@ -354,8 +378,12 @@ impl<T> ListViewRow<T> {
|
|||
crate::view::View {
|
||||
delegate: None,
|
||||
is_handle: true,
|
||||
layer: Layer::new(),
|
||||
layer: Layer::new(), // @TODO: Fix & return cloned true layer for this row.
|
||||
objc: self.objc.clone(),
|
||||
animator: self.animator.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: self.safe_layout_guide.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: self.top.clone(),
|
||||
|
@ -408,16 +436,18 @@ impl<T> ListViewRow<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ListViewRow<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for ListViewRow<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ListViewRow<T> {}
|
||||
|
||||
impl<T> Drop for ListViewRow<T> {
|
||||
fn drop(&mut self) {
|
||||
}
|
||||
|
|
|
@ -36,8 +36,9 @@ pub trait ListViewDelegate {
|
|||
/// had time to sit down and figure them out properly yet.
|
||||
fn item_for(&self, row: usize) -> ListViewRow;
|
||||
|
||||
/// Called when an item has been selected (clicked/tapped on).
|
||||
fn item_selected(&self, row: usize) {}
|
||||
/// Called when an item has been selected (clicked/tapped on). If the selection was cleared,
|
||||
/// then this will be called with `None`.
|
||||
fn item_selected(&self, row: Option<usize>) {}
|
||||
|
||||
/// Called when the menu for the tableview is about to be shown. You can update the menu here
|
||||
/// depending on, say, what the user has context-clicked on. You should avoid any expensive
|
||||
|
|
23
src/objc_access.rs
Normal file
23
src/objc_access.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
//! Implements a parent trait for the various sub-traits we use throughout Cacao. The methods
|
||||
//! defined on here provide access handlers for common properties that the sub-traits need to
|
||||
//! enable modifying.
|
||||
|
||||
use objc::runtime::Object;
|
||||
|
||||
use crate::foundation::id;
|
||||
|
||||
/// Types that implement this should provide access to their underlying root node type (e.g, the
|
||||
/// view or control). Traits that have this as their super-trait can rely on this to ensure access
|
||||
/// without needing to derive or do extra work elsewhere.
|
||||
#[allow(unused_variables)]
|
||||
pub trait ObjcAccess {
|
||||
/// Used for mutably interacting with the underlying Objective-C instance.
|
||||
/// Setters should use this.
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F);
|
||||
|
||||
/// Used for checking backing properties of the underlying Objective-C instance, without
|
||||
/// needing a mutable borrow.
|
||||
///
|
||||
/// Getters should use this.
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R;
|
||||
}
|
|
@ -20,7 +20,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use objc_id::ShareId;
|
||||
use url::Url;
|
||||
|
||||
use crate::foundation::{id, nil, NSString, NSArray};
|
||||
use crate::foundation::{id, nil, NSString, NSArray, NSURL};
|
||||
use crate::error::Error;
|
||||
|
||||
mod types;
|
||||
|
@ -93,7 +93,7 @@ impl Pasteboard {
|
|||
/// _Note that this method returns a list of `Url` entities, in an attempt to be closer to how
|
||||
/// Cocoa & co operate. This method may go away in the future if it's determined that people
|
||||
/// wind up just using `get_file_paths()`._
|
||||
pub fn get_file_urls(&self) -> Result<Vec<Url>, Box<dyn std::error::Error>> {
|
||||
pub fn get_file_urls(&self) -> Result<Vec<NSURL>, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
let class: id = msg_send![class!(NSURL), class];
|
||||
let classes = NSArray::new(&[class]);
|
||||
|
@ -113,40 +113,7 @@ impl Pasteboard {
|
|||
}
|
||||
|
||||
let urls = NSArray::retain(contents).map(|url| {
|
||||
let path = NSString::retain(msg_send![url, path]);
|
||||
Url::parse(&format!("file://{}", path.to_str()))
|
||||
}).into_iter().filter_map(|r| r.ok()).collect();
|
||||
|
||||
Ok(urls)
|
||||
}
|
||||
}
|
||||
|
||||
/// Looks inside the pasteboard contents and extracts what FileURLs are there, if any.
|
||||
///
|
||||
/// Note that this method operates on file paths, as opposed to URLs, and returns a list of
|
||||
/// results in a format more Rust-y.
|
||||
pub fn get_file_paths(&self) -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
|
||||
unsafe {
|
||||
let class: id = msg_send![class!(NSURL), class];
|
||||
let classes = NSArray::new(&[class]);
|
||||
let contents: id = msg_send![&*self.0, readObjectsForClasses:classes options:nil];
|
||||
|
||||
// This can happen if the Pasteboard server has an error in returning items.
|
||||
// In our case, we'll bubble up an error by checking the pasteboard.
|
||||
if contents == nil {
|
||||
// This error is not necessarily "correct", but in the event of an error in
|
||||
// Pasteboard server retrieval I'm not sure where to check... and this stuff is
|
||||
// kinda ancient and has conflicting docs in places. ;P
|
||||
return Err(Box::new(Error {
|
||||
code: 666,
|
||||
domain: "com.cacao-rs.pasteboard".to_string(),
|
||||
description: "Pasteboard server returned no data.".to_string()
|
||||
}));
|
||||
}
|
||||
|
||||
let urls = NSArray::retain(contents).map(|url| {
|
||||
let path = NSString::retain(msg_send![url, path]).to_str().to_string();
|
||||
PathBuf::from(path)
|
||||
NSURL::retain(url)
|
||||
}).into_iter().collect();
|
||||
|
||||
Ok(urls)
|
||||
|
|
|
@ -20,6 +20,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use crate::foundation::{id, nil, YES, NO, NSUInteger};
|
||||
use crate::color::Color;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
|
@ -201,16 +202,18 @@ impl ProgressIndicator {
|
|||
}
|
||||
}
|
||||
|
||||
impl Layout for ProgressIndicator {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl ObjcAccess for ProgressIndicator {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for ProgressIndicator {}
|
||||
|
||||
impl Drop for ProgressIndicator {
|
||||
/// A bit of extra cleanup for delegate callback pointers.
|
||||
/// If the originating `ProgressIndicator` is being
|
||||
|
|
|
@ -83,7 +83,7 @@ impl Default for ThumbnailConfig {
|
|||
|
||||
// #TODO: Should query the current screen size maybe? 2x is fairly safe
|
||||
// for most moderns Macs right now.
|
||||
scale: 2.,
|
||||
scale: 1.,
|
||||
|
||||
minimum_dimension: 0.,
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::Path;
|
||||
|
||||
use objc::runtime::{Object};
|
||||
use objc::runtime::Object;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use objc_id::ShareId;
|
||||
|
||||
|
@ -17,13 +17,19 @@ pub use config::{ThumbnailConfig, ThumbnailQuality};
|
|||
pub struct ThumbnailGenerator(pub ShareId<Object>);
|
||||
|
||||
impl ThumbnailGenerator {
|
||||
/// Returns the global shared, wrapped, QLThumbnailGenerator.
|
||||
pub fn shared() -> Self {
|
||||
ThumbnailGenerator(unsafe {
|
||||
ShareId::from_ptr(msg_send![class!(QLThumbnailGenerator), sharedGenerator])
|
||||
})
|
||||
}
|
||||
|
||||
pub fn generate<F>(&self, path: &Path, config: ThumbnailConfig, callback: F)
|
||||
/// Given a path and config, will generate a preview image, calling back on the provided
|
||||
/// callback closure.
|
||||
///
|
||||
/// Note that this callback can come back on a separate thread, so react accordingly to get to
|
||||
/// the main thread if you need to.
|
||||
pub fn generate_from_path<F>(&self, path: &Path, config: ThumbnailConfig, callback: F)
|
||||
where
|
||||
F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static
|
||||
{
|
||||
|
|
|
@ -47,8 +47,8 @@ use objc::{msg_send, sel, sel_impl};
|
|||
|
||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
||||
use crate::color::Color;
|
||||
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::pasteboard::PasteboardType;
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
|
@ -300,16 +300,18 @@ impl<T> ScrollView<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ScrollView<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for ScrollView<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for ScrollView<T> {}
|
||||
|
||||
impl<T> Drop for ScrollView<T> {
|
||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||
|
|
272
src/select/mod.rs
Normal file
272
src/select/mod.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
//! Implements a Select-style dropdown. By default this uses NSPopupSelect on macOS.
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use core_graphics::geometry::CGRect;
|
||||
|
||||
use objc_id::ShareId;
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel};
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
|
||||
use crate::control::Control;
|
||||
use crate::foundation::{id, nil, YES, NO, NSString, NSInteger};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::geometry::Rect;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
|
||||
/// Wraps `NSPopUpSelect` on AppKit. Not currently implemented for iOS.
|
||||
///
|
||||
/// Acts like a `<select>` dropdown, if you're familiar with HTML. Use for dropdown option
|
||||
/// selecting.
|
||||
///
|
||||
/// Some properties are platform-specific; see the documentation for further information.
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// let mut dropdown = Select::new();
|
||||
///
|
||||
/// // Make sure you don't let your Select drop for as long as you need it.
|
||||
/// my_view.add_subview(&dropdown);
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Select {
|
||||
/// A handle for the underlying Objective-C object.
|
||||
pub objc: ObjcProperty,
|
||||
|
||||
handler: Option<TargetActionHandler>,
|
||||
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub top: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime leading layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub leading: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime left layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub left: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime trailing layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub trailing: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime right layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub right: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime bottom layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub bottom: LayoutAnchorY,
|
||||
|
||||
/// A pointer to the Objective-C runtime width layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub width: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime height layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub height: LayoutAnchorDimension,
|
||||
|
||||
/// A pointer to the Objective-C runtime center X layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub center_x: LayoutAnchorX,
|
||||
|
||||
/// A pointer to the Objective-C runtime center Y layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub center_y: LayoutAnchorY
|
||||
}
|
||||
|
||||
impl Select {
|
||||
/// Creates a new `Select` instance, configures it appropriately,
|
||||
/// and retains the necessary Objective-C runtime pointer.
|
||||
pub fn new() -> Self {
|
||||
let zero: CGRect = Rect::zero().into();
|
||||
|
||||
let view: id = unsafe {
|
||||
let alloc: id = msg_send![register_class(), alloc];
|
||||
let select: id = msg_send![alloc, initWithFrame:zero pullsDown:NO];
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
let _: () = msg_send![select, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
select
|
||||
};
|
||||
|
||||
Select {
|
||||
handler: None,
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
left: LayoutAnchorX::left(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
leading: LayoutAnchorX::leading(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
right: LayoutAnchorX::right(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
trailing: LayoutAnchorX::trailing(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
bottom: LayoutAnchorY::bottom(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
width: LayoutAnchorDimension::width(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
height: LayoutAnchorDimension::height(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
center_x: LayoutAnchorX::center(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
center_y: LayoutAnchorY::center(view),
|
||||
|
||||
objc: ObjcProperty::retain(view),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attaches a callback for selection events.
|
||||
/// Much like `Button`, this really needs to be revisited.
|
||||
///
|
||||
/// Really, this is not ideal.
|
||||
///
|
||||
/// I cannot stress this enough.
|
||||
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
|
||||
// @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
|
||||
let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
|
||||
let handler = TargetActionHandler::new(&*this, action);
|
||||
self.handler = Some(handler);
|
||||
}
|
||||
|
||||
/// Sets whether this pulls down (dropdown) or pops up.
|
||||
pub fn set_pulls_down(&self, pulls_down: bool) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setPullsDown:match pulls_down {
|
||||
true => YES,
|
||||
false => NO
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds an item to the dropdown list.
|
||||
pub fn add_item(&self, title: &str) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let s = NSString::new(title);
|
||||
let _: () = msg_send![obj, addItemWithTitle:s];
|
||||
});
|
||||
}
|
||||
|
||||
/// Removes all items from the dropdown list.
|
||||
pub fn remove_all_items(&self) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, removeAllItems];
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove the item at the specified index.
|
||||
pub fn remove_item_at_index(&self, index: usize) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, removeItemAtIndex:index];
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_selected_index(&self, index: NSInteger) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, selectItemAtIndex:index];
|
||||
});
|
||||
}
|
||||
|
||||
/// Gets the selected index.
|
||||
pub fn get_selected_index(&self) -> usize {
|
||||
self.objc.get(|obj| unsafe {
|
||||
let index: NSInteger = msg_send![obj, indexOfSelectedItem];
|
||||
index as usize
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the number of items in the dropdown.
|
||||
pub fn len(&self) -> usize {
|
||||
self.objc.get(|obj| unsafe {
|
||||
let index: NSInteger = msg_send![obj, numberOfItems];
|
||||
index as usize
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjcAccess for Select {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Select {
|
||||
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||
panic!(r#"
|
||||
Tried to add a subview to a Select. This is not allowed in Cacao. If you think this should be supported,
|
||||
open a discussion on the GitHub repo.
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
impl Control for Select {}
|
||||
|
||||
impl ObjcAccess for &Select {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for &Select {
|
||||
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||
panic!(r#"
|
||||
Tried to add a subview to a Select. This is not allowed in Cacao. If you think this should be supported,
|
||||
open a discussion on the GitHub repo.
|
||||
"#);
|
||||
}
|
||||
}
|
||||
|
||||
impl Control for &Select {}
|
||||
|
||||
impl Drop for Select {
|
||||
/// Nils out references on the Objective-C side and removes this from the backing view.
|
||||
// Just to be sure, let's... nil these out. They should be weak references,
|
||||
// but I'd rather be paranoid and remove them later.
|
||||
fn drop(&mut self) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setTarget:nil];
|
||||
let _: () = msg_send![obj, setAction:nil];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers an `NSSelect` subclass, and configures it to hold some ivars
|
||||
/// for various things we need to store.
|
||||
fn register_class() -> *const Class {
|
||||
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = class!(NSPopUpButton);
|
||||
let decl = ClassDecl::new("CacaoSelect", superclass).unwrap();
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe { VIEW_CLASS }
|
||||
}
|
|
@ -12,6 +12,7 @@ use objc::{class, msg_send, sel, sel_impl};
|
|||
use crate::foundation::{id, nil, BOOL, YES, NO, NSString};
|
||||
use crate::invoker::TargetActionHandler;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::{load, properties::ObjcProperty};
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
|
@ -143,15 +144,17 @@ impl Switch {
|
|||
}
|
||||
}
|
||||
|
||||
impl Layout for Switch {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl ObjcAccess for Switch {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Layout for Switch {
|
||||
fn add_subview<V: Layout>(&self, _view: &V) {
|
||||
panic!(r#"
|
||||
Tried to add a subview to a Switch. This is not allowed in Cacao. If you think this should be supported,
|
||||
|
|
|
@ -12,8 +12,11 @@ use objc_id::Id;
|
|||
use crate::color::Color;
|
||||
use crate::foundation::{id, to_bool, BOOL, YES, NO, NSString};
|
||||
|
||||
use super::Font;
|
||||
|
||||
extern "C" {
|
||||
static NSForegroundColorAttributeName: id;
|
||||
static NSFontAttributeName: id;
|
||||
}
|
||||
|
||||
/// A wrapper around `NSMutableAttributedString`, which can be used for more complex text
|
||||
|
@ -54,6 +57,16 @@ impl AttributedString {
|
|||
];
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the font for the specified range.
|
||||
pub fn set_font(&mut self, font: Font, range: Range<isize>) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.0, addAttribute:NSFontAttributeName
|
||||
value:&*font
|
||||
range:range
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AttributedString {
|
||||
|
|
|
@ -47,7 +47,8 @@ use objc::{msg_send, sel, sel_impl};
|
|||
use crate::foundation::{id, nil, YES, NO, NSArray, NSInteger, NSUInteger, NSString};
|
||||
use crate::color::Color;
|
||||
use crate::layout::Layout;
|
||||
use crate::text::{Font, TextAlign, LineBreakMode};
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::text::{AttributedString, Font, TextAlign, LineBreakMode};
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
|
@ -77,7 +78,12 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
|||
let view: id = {
|
||||
// This sucks, but for now, sure.
|
||||
let blank = NSString::no_copy("");
|
||||
msg_send![registration_fn(), labelWithString:&*blank]
|
||||
let label: id = msg_send![registration_fn(), wrappingLabelWithString:&*blank];
|
||||
|
||||
// We sub this in to get the general expected behavior for 202*.
|
||||
let _: () = msg_send![label, setSelectable:NO];
|
||||
|
||||
label
|
||||
};
|
||||
|
||||
#[cfg(feature = "uikit")]
|
||||
|
@ -361,6 +367,13 @@ impl<T> Label<T> {
|
|||
});
|
||||
}
|
||||
|
||||
/// Sets the attributed string to be the attributed string value on this label.
|
||||
pub fn set_attributed_text(&self, text: AttributedString) {
|
||||
self.objc.with_mut(|obj| unsafe {
|
||||
let _: () = msg_send![obj, setAttributedStringValue:&*text];
|
||||
});
|
||||
}
|
||||
|
||||
/// Retrieve the text currently held in the label.
|
||||
pub fn get_text(&self) -> String {
|
||||
self.objc.get(|obj| unsafe {
|
||||
|
@ -416,16 +429,18 @@ impl<T> Label<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for Label<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for Label<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for Label<T> {}
|
||||
|
||||
impl<T> Drop for Label<T> {
|
||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `Label` is being
|
||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use crate::foundation::NSUInteger;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NotificationAuthOption {
|
||||
Badge,
|
||||
Sound,
|
||||
|
|
|
@ -18,6 +18,7 @@ pub mod notifications;
|
|||
pub use notifications::Notification;
|
||||
|
||||
/// Acts as a central interface to the Notification Center on macOS.
|
||||
#[derive(Debug)]
|
||||
pub struct NotificationCenter;
|
||||
|
||||
impl NotificationCenter {
|
||||
|
@ -26,7 +27,7 @@ impl NotificationCenter {
|
|||
unsafe {
|
||||
// @TODO: Revisit.
|
||||
let block = ConcreteBlock::new(|_: id, error: id| {
|
||||
let localized_description = NSString::wrap(msg_send![error, localizedDescription]);
|
||||
let localized_description = NSString::new(msg_send![error, localizedDescription]);
|
||||
let e = localized_description.to_str();
|
||||
if e != "" {
|
||||
println!("{:?}", e);
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::foundation::{id, NSString};
|
|||
|
||||
/// A wrapper for `UNMutableNotificationContent`. Retains the pointer from the Objective C side,
|
||||
/// and is ultimately dropped upon sending.
|
||||
#[derive(Debug)]
|
||||
pub struct Notification(pub Id<Object>);
|
||||
|
||||
impl Notification {
|
||||
|
|
26
src/view/animator.rs
Normal file
26
src/view/animator.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use core_graphics::base::CGFloat;
|
||||
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use objc::runtime::{Class, Object};
|
||||
use objc_id::ShareId;
|
||||
|
||||
use crate::foundation::id;
|
||||
|
||||
/// A wrapper for an animation proxy object in Cocoa that supports basic animations.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ViewAnimatorProxy(pub ShareId<Object>);
|
||||
|
||||
impl ViewAnimatorProxy {
|
||||
pub fn new(proxy: id) -> Self {
|
||||
Self(unsafe {
|
||||
ShareId::from_ptr(msg_send![proxy, animator])
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the alpha value for the view being animated.
|
||||
pub fn set_alpha(&self, value: CGFloat) {
|
||||
unsafe {
|
||||
let _: () = msg_send![&*self.0, setAlphaValue:value];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ use objc::{msg_send, sel, sel_impl};
|
|||
|
||||
use crate::foundation::id;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::view::{VIEW_DELEGATE_PTR, View, ViewDelegate};
|
||||
use crate::utils::Controller;
|
||||
|
||||
|
@ -55,7 +56,7 @@ where
|
|||
(&mut *vc).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
|
||||
}
|
||||
|
||||
view.with_backing_node(|backing_node| {
|
||||
view.with_backing_obj_mut(|backing_node| {
|
||||
let _: () = msg_send![vc, setView:backing_node];
|
||||
});
|
||||
|
||||
|
|
|
@ -48,14 +48,18 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
|||
use crate::color::Color;
|
||||
use crate::layer::Layer;
|
||||
use crate::layout::Layout;
|
||||
use crate::objc_access::ObjcAccess;
|
||||
use crate::utils::properties::ObjcProperty;
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||
use crate::layout::{LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension, SafeAreaLayoutGuide};
|
||||
|
||||
#[cfg(feature = "appkit")]
|
||||
use crate::pasteboard::PasteboardType;
|
||||
|
||||
mod animator;
|
||||
pub use animator::ViewAnimatorProxy;
|
||||
|
||||
#[cfg_attr(feature = "appkit", path = "appkit.rs")]
|
||||
#[cfg_attr(feature = "uikit", path = "uikit.rs")]
|
||||
mod native_interface;
|
||||
|
@ -88,6 +92,9 @@ pub struct View<T = ()> {
|
|||
/// A pointer to the Objective-C runtime view controller.
|
||||
pub objc: ObjcProperty,
|
||||
|
||||
/// An object that supports limited animations. Can be cloned into animation closures.
|
||||
pub animator: ViewAnimatorProxy,
|
||||
|
||||
/// References the underlying layer. This is consistent across AppKit & UIKit - in AppKit
|
||||
/// we explicitly opt in to layer backed views.
|
||||
pub layer: Layer,
|
||||
|
@ -95,6 +102,10 @@ pub struct View<T = ()> {
|
|||
/// A pointer to the delegate for this view.
|
||||
pub delegate: Option<Box<T>>,
|
||||
|
||||
/// A property containing safe layout guides.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub safe_layout_guide: SafeAreaLayoutGuide,
|
||||
|
||||
/// A pointer to the Objective-C runtime top layout constraint.
|
||||
#[cfg(feature = "autolayout")]
|
||||
pub top: LayoutAnchorY,
|
||||
|
@ -162,6 +173,9 @@ impl View {
|
|||
is_handle: false,
|
||||
delegate: None,
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: SafeAreaLayoutGuide::new(view),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: LayoutAnchorY::top(view),
|
||||
|
||||
|
@ -196,6 +210,7 @@ impl View {
|
|||
msg_send![view, layer]
|
||||
}),
|
||||
|
||||
animator: ViewAnimatorProxy::new(view),
|
||||
objc: ObjcProperty::retain(view),
|
||||
}
|
||||
}
|
||||
|
@ -231,16 +246,20 @@ impl<T> View<T> where T: ViewDelegate + 'static {
|
|||
}
|
||||
|
||||
impl<T> View<T> {
|
||||
/// An internal method that returns a clone of this object, sans references to the delegate or
|
||||
/// Returns a clone of this object, sans references to the delegate or
|
||||
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
|
||||
/// reference, customize and use the view but without the trickery of holding pieces of the
|
||||
/// delegate - the `View` is the only true holder of those.
|
||||
pub(crate) fn clone_as_handle(&self) -> View {
|
||||
pub fn clone_as_handle(&self) -> View {
|
||||
View {
|
||||
delegate: None,
|
||||
is_handle: true,
|
||||
layer: self.layer.clone(),
|
||||
objc: self.objc.clone(),
|
||||
animator: self.animator.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
safe_layout_guide: self.safe_layout_guide.clone(),
|
||||
|
||||
#[cfg(feature = "autolayout")]
|
||||
top: self.top.clone(),
|
||||
|
@ -291,16 +310,18 @@ impl<T> View<T> {
|
|||
|
||||
}
|
||||
|
||||
impl<T> Layout for View<T> {
|
||||
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
|
||||
impl<T> ObjcAccess for View<T> {
|
||||
fn with_backing_obj_mut<F: Fn(id)>(&self, handler: F) {
|
||||
self.objc.with_mut(handler);
|
||||
}
|
||||
|
||||
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
fn get_from_backing_obj<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
|
||||
self.objc.get(handler)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Layout for View<T> {}
|
||||
|
||||
impl<T> Drop for View<T> {
|
||||
/// If the instance being dropped is _not_ a handle, then we want to go ahead and explicitly
|
||||
/// remove it from any super views.
|
||||
|
|
Loading…
Reference in a new issue