More general updates.

- Filesystem Save/Open panels can return PathBuf's instead of Url's,
  which feels more native to Rust.
- Support for drawing into an Image context with Core Graphics.
- ListView swipe-to-reveal action support.
- Experimental ListView cell reuse support.
- Updates to QuickLook to also support PathBuf's.
This commit is contained in:
Ryan McGrath 2021-02-04 13:34:12 -08:00
parent 62cebab691
commit 22f96bb238
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
16 changed files with 590 additions and 53 deletions

View file

@ -9,10 +9,13 @@ repository = "https://github.com/ryanmcgrath/cacao"
categories = ["gui", "os::macos-apis", "os::ios-apis"]
keywords = ["gui", "macos", "ios", "appkit", "uikit"]
[package.metadata.docs.rs]
default-target = "x86_64-apple-darwin"
[dependencies]
block = "0.1.6"
core-foundation = { version = "0.7", features = ["with-chrono"] }
core-graphics = "0.19.0"
core-foundation = { version = "0.9", features = ["with-chrono", "mac_os_10_8_features"] }
core-graphics = "0.22"
dispatch = "0.2.0"
lazy_static = "1.4.0"
libc = "0.2"
@ -26,6 +29,7 @@ default = ["macos"]
cloudkit = []
ios = []
macos = []
quicklook = []
user-notifications = ["uuid"]
webview = []
webview-downloading = []

View file

@ -2,6 +2,7 @@
//! for (what I believe) is a friendlier API.
use core_graphics::base::CGFloat;
use core_graphics::color::CGColor;
use objc::{class, msg_send, sel, sel_impl};
@ -69,6 +70,16 @@ impl Color {
}
}
/// Maps to CGColor, used across platforms.
pub fn cg_color(&self) -> CGColor {
let red = self.red as CGFloat / 255.0;
let green = self.green as CGFloat / 255.0;
let blue = self.blue as CGFloat / 255.0;
let alpha = self.alpha as CGFloat / 255.0;
CGColor::rgb(red, green, blue, alpha)
}
/// Returns the red channel in a floating point number form, from 0 to 1.
#[inline]
pub fn red_f32(&self) -> f32 {

View file

@ -2,6 +2,8 @@
//! urls to work with. It currently doesn't implement _everything_ necessary, but it's functional
//! enough for general use.
use std::path::PathBuf;
use block::ConcreteBlock;
use objc::{class, msg_send, sel, sel_impl};
@ -120,7 +122,7 @@ impl FileSelectPanel {
/// 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
/// retain/ownership rules here.
pub fn show<F: Fn(Vec<String>) + 'static>(&self, handler: F) {
pub fn show<F: Fn(Vec<PathBuf>) + 'static>(&self, handler: F) {
let panel = self.panel.clone();
let completion = ConcreteBlock::new(move |result: NSInteger| {
let response: ModalResponse = result.into();
@ -140,8 +142,8 @@ 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<String> {
let mut paths: Vec<String> = vec![];
pub fn get_urls(panel: &Object) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = vec![];
unsafe {
let urls: id = msg_send![&*panel, URLs];
@ -153,8 +155,8 @@ pub fn get_urls(panel: &Object) -> Vec<String> {
}
let url: id = msg_send![urls, objectAtIndex:count-1];
let path = NSString::wrap(msg_send![url, absoluteString]).to_str().to_string();
paths.push(path);
let path = NSString::wrap(msg_send![url, path]).to_str().to_string();
paths.push(path.into());
count -= 1;
}
}

View file

@ -1,16 +1,158 @@
use objc_id::ShareId;
use objc::runtime::Object;
use crate::foundation::{id};
use objc::{class, msg_send, sel, sel_impl};
use block::ConcreteBlock;
use core_graphics::{
base::{CGFloat},
geometry::{CGRect, CGPoint, CGSize}
};
use core_graphics::context::{CGContext, CGContextRef};
use crate::foundation::{id, YES, NO};
#[derive(Debug)]
pub enum ResizeBehavior {
AspectFit,
AspectFill,
Stretch,
Center
}
fn max_cgfloat(x: CGFloat, y: CGFloat) -> CGFloat {
if x == y { return x; }
match x > y {
true => x,
false => y
}
}
fn min_cgfloat(x: CGFloat, y: CGFloat) -> CGFloat {
if x == y { return x; }
match x < y {
true => x,
false => y
}
}
impl ResizeBehavior {
pub fn apply(&self, source: CGRect, target: CGRect) -> CGRect {
// if equal, just return source
if(
source.origin.x == target.origin.x &&
source.origin.y == target.origin.y &&
source.size.width == target.size.width &&
source.size.height == target.size.height
) {
return source;
}
if(
source.origin.x == 0. &&
source.origin.y == 0. &&
source.size.width == 0. &&
source.size.height == 0.
) {
return source;
}
let mut scales = CGSize::new(0., 0.);
scales.width = (target.size.width / source.size.width).abs();
scales.height = (target.size.height / source.size.height).abs();
match self {
ResizeBehavior::AspectFit => {
scales.width = min_cgfloat(scales.width, scales.height);
scales.height = scales.width;
},
ResizeBehavior::AspectFill => {
scales.width = max_cgfloat(scales.width, scales.height);
scales.height = scales.width;
},
ResizeBehavior::Stretch => { /* will do this as default */ },
ResizeBehavior::Center => {
scales.width = 1.;
scales.height = 1.;
}
}
let mut result = source;
result.size.width *= scales.width;
result.size.height *= scales.height;
result.origin.x = target.origin.x + (target.size.width - result.size.width) / 2.;
result.origin.y = target.origin.y + (target.size.height - result.size.height) / 2.;
result
}
}
#[derive(Debug)]
pub struct DrawConfig {
pub source: (CGFloat, CGFloat),
pub target: (CGFloat, CGFloat),
pub resize: ResizeBehavior
}
#[derive(Clone, Debug)]
pub struct Image(pub ShareId<Object>);
impl Image {
/// Wraps a system-returned image, e.g from QuickLook previews.
pub fn with(image: id) -> Self {
Image(unsafe {
ShareId::from_ptr(image)
})
}
}
/// Draw a custom image and get it back as a returned `Image`.
pub fn draw<F>(config: DrawConfig, handler: F) -> Self
where
F: Fn(CGRect, &CGContextRef) -> bool + 'static
{
let source_frame = CGRect::new(
&CGPoint::new(0., 0.),
&CGSize::new(config.source.0, config.source.1)
);
let target_frame = CGRect::new(
&CGPoint::new(0., 0.),
&CGSize::new(config.target.0, config.target.1)
);
let resized_frame = config.resize.apply(source_frame, target_frame);
let block = ConcreteBlock::new(move |_destination: CGRect| unsafe {
let current_context: id = msg_send![class!(NSGraphicsContext), currentContext];
let context_ptr: core_graphics::sys::CGContextRef = msg_send![current_context, CGContext];
let context = CGContext::from_existing_context_ptr(context_ptr);
let _: () = msg_send![class!(NSGraphicsContext), saveGraphicsState];
context.translate(resized_frame.origin.x, resized_frame.origin.y);
context.scale(
resized_frame.size.width / config.source.0,
resized_frame.size.height / config.source.1
);
let result = handler(resized_frame, &context);
let _: () = msg_send![class!(NSGraphicsContext), restoreGraphicsState];
match result {
true => YES,
false => NO
}
});
let block = block.copy();
Image(unsafe {
let img: id = msg_send![class!(NSImage), imageWithSize:target_frame.size flipped:YES drawingHandler:block];
ShareId::from_ptr(img)
})
}
}

View file

@ -19,7 +19,7 @@ mod ios;
use ios::register_image_view_class;
mod image;
pub use image::Image;
pub use image::{Image, DrawConfig, ResizeBehavior};
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
@ -37,7 +37,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
/// side anyway.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ImageView {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,

107
src/listview/actions.rs Normal file
View file

@ -0,0 +1,107 @@
use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use block::ConcreteBlock;
use crate::color::Color;
use crate::foundation::{id, NSString, NSUInteger};
use crate::image::Image;
/// Represents the "type" or "style" of row action. A `Regular` action is
/// nothing special; do whatever you want. A `Destructive` action will have
/// a special animation when being deleted, among any other system specialties.
#[derive(Debug)]
pub enum RowActionStyle {
/// The stock, standard, regular action.
Regular,
/// Use this to denote that an action is destructive.
Destructive
}
impl Default for RowActionStyle {
fn default() -> Self {
RowActionStyle::Regular
}
}
impl From<RowActionStyle> for NSUInteger {
fn from(style: RowActionStyle) -> Self {
match style {
RowActionStyle::Regular => 0,
RowActionStyle::Destructive => 1
}
}
}
/// Represents an action that can be displayed when a user swipes-to-reveal
/// on a ListViewRow. You return this from the appropriate delegate method,
/// and the system will handle displaying the necessary pieces for you.
#[derive(Debug)]
pub struct RowAction(pub Id<Object>);
impl RowAction {
/// Creates and returns a new `RowAction`. You'd use this handler to
/// configure whatever action you want to show when a user swipes-to-reveal
/// on your ListViewRow.
///
/// Additional configuration can be done after initialization, if need be.
pub fn new<F>(title: &str, style: RowActionStyle, handler: F) -> Self
where
F: Fn(RowAction, usize) + Send + Sync + 'static
{
let title = NSString::new(title);
let block = ConcreteBlock::new(move |action: id, row: NSUInteger| {
let action = RowAction(unsafe {
Id::from_ptr(action)
});
handler(action, row as usize);
});
let block = block.copy();
let style = style as NSUInteger;
RowAction(unsafe {
let cls = class!(NSTableViewRowAction);
Id::from_ptr(msg_send![cls, rowActionWithStyle:style
title:title.into_inner()
handler:block
])
})
}
/// Sets the title of this action.
pub fn set_title(&mut self, title: &str) {
let title = NSString::new(title);
unsafe {
let _: () = msg_send![&*self.0, setTitle:title.into_inner()];
}
}
/// Sets the background color of this action.
pub fn set_background_color(&mut self, color: Color) {
let color = color.into_platform_specific_color();
unsafe {
let _: () = msg_send![&*self.0, setBackgroundColor:color];
}
}
/// Sets the style of this action.
pub fn set_style(&mut self, style: RowActionStyle) {
let style = style as NSUInteger;
unsafe {
let _: () = msg_send![&*self.0, setStyle:style];
}
}
/// Sets an optional image for this action.
pub fn set_image(&mut self, image: Image) {
unsafe {
let _: () = msg_send![&*self.0, setImage:&*image.0];
}
}
}

View file

@ -1,9 +1,9 @@
use crate::foundation::NSUInteger;
use crate::foundation::{NSInteger, NSUInteger};
/// This enum represents the different stock animations possible
/// for ListView row operations. You can pass it to `insert_rows`
/// and `remove_rows` - reloads don't get animations.
pub enum ListViewAnimation {
pub enum RowAnimation {
/// No animation.
None,
@ -27,16 +27,36 @@ pub enum ListViewAnimation {
SlideRight
}
impl Into<NSUInteger> for ListViewAnimation {
impl Into<NSUInteger> for RowAnimation {
fn into(self) -> NSUInteger {
match self {
ListViewAnimation::None => 0x0,
ListViewAnimation::Fade => 0x1,
ListViewAnimation::Gap => 0x2,
ListViewAnimation::SlideUp => 0x10,
ListViewAnimation::SlideDown => 0x20,
ListViewAnimation::SlideLeft => 0x30,
ListViewAnimation::SlideRight => 0x40
RowAnimation::None => 0x0,
RowAnimation::Fade => 0x1,
RowAnimation::Gap => 0x2,
RowAnimation::SlideUp => 0x10,
RowAnimation::SlideDown => 0x20,
RowAnimation::SlideLeft => 0x30,
RowAnimation::SlideRight => 0x40
}
}
}
#[derive(Debug)]
pub enum RowEdge {
Leading,
Trailing
}
impl Into<RowEdge> for NSInteger {
fn into(self) -> RowEdge {
match self {
0 => RowEdge::Leading,
1 => RowEdge::Trailing,
// @TODO: This *should* be unreachable, provided macOS doesn't start
// letting people swipe from vertical directions to reveal stuff. Have to
// feel like there's a better way to do this, though...
_ => { unreachable!(); }
}
}
}

View file

@ -14,9 +14,12 @@ use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl, msg_send};
use objc_id::Id;
use crate::foundation::{id, YES, NO, NSInteger, NSUInteger};
use crate::foundation::{id, YES, NO, NSArray, NSInteger, NSUInteger};
use crate::dragdrop::DragInfo;
use crate::listview::{LISTVIEW_DELEGATE_PTR, ListViewDelegate};
use crate::listview::{
LISTVIEW_DELEGATE_PTR, LISTVIEW_CELL_VENDOR_PTR,
ListViewDelegate, RowEdge
};
use crate::utils::load;
/// Determines the number of items by way of the backing data source (the Rust struct).
@ -32,12 +35,12 @@ extern fn number_of_items<T: ListViewDelegate>(
extern fn view_for_column<T: ListViewDelegate>(
this: &Object,
_: Sel,
table_view: id,
_table_view: id,
_: id,
item: NSInteger
) -> id {
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
let item = view.item(item as usize);
let item = view.item_for(item as usize);
// A hacky method of returning the underlying pointer
// without Rust annoying us.
@ -46,10 +49,30 @@ extern fn view_for_column<T: ListViewDelegate>(
// as we *know* the underlying view will be retained by the NSTableView, so
// passing over one more won't really screw up retain counts.
unsafe {
msg_send![&*item, self]
msg_send![&*item.objc, self]
}
}
extern fn row_actions_for_row<T: ListViewDelegate>(
this: &Object,
_: Sel,
_table_view: id,
row: NSInteger,
edge: NSInteger
) -> id {
let edge: RowEdge = edge.into();
let view = load::<T>(this, LISTVIEW_DELEGATE_PTR);
let actions = view.actions_for(row as usize, edge);
//if actions.len() > 0 {
let ids: Vec<&Object> = actions.iter().map(|action| &*action.0).collect();
NSArray::from(ids).into_inner()
//} else {
// NSArray::new(&[]).into_inner()
//}
}
/// Enforces normalcy, or: a needlessly cruel method in terms of the name. You get the idea though.
extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
return YES;
@ -140,12 +163,14 @@ pub(crate) fn register_listview_class_with_delegate<T: ListViewDelegate>() -> *c
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't
// move.
decl.add_ivar::<usize>(LISTVIEW_DELEGATE_PTR);
decl.add_ivar::<usize>(LISTVIEW_CELL_VENDOR_PTR);
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
// Tableview-specific
decl.add_method(sel!(numberOfRowsInTableView:), number_of_items::<T> as extern fn(&Object, _, 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:rowActionsForRow:edge:), row_actions_for_row::<T> as extern fn(&Object, _, id, NSInteger, NSInteger) -> id);
// Drag and drop operations (e.g, accepting files)
decl.add_method(sel!(draggingEntered:), dragging_entered::<T> as extern fn (&mut Object, _, _) -> NSUInteger);

View file

@ -41,6 +41,8 @@
//!
//! For more information on Autolayout, view the module or check out the examples folder.
use std::collections::HashMap;
use core_graphics::base::CGFloat;
use objc_id::ShareId;
use objc::runtime::{Class, Object};
@ -66,7 +68,7 @@ mod ios;
use ios::{register_view_class, register_view_class_with_delegate};
mod enums;
pub use enums::ListViewAnimation;
pub use enums::{RowAnimation, RowEdge};
mod traits;
pub use traits::ListViewDelegate;
@ -74,7 +76,68 @@ pub use traits::ListViewDelegate;
mod row;
pub use row::ListViewRow;
mod actions;
pub use actions::{RowAction, RowActionStyle};
pub(crate) static LISTVIEW_DELEGATE_PTR: &str = "rstListViewDelegatePtr";
pub(crate) static LISTVIEW_CELL_VENDOR_PTR: &str = "rstListViewCellVendorPtr";
use std::any::Any;
use std::sync::{Arc, RwLock};
use std::rc::Rc;
use std::cell::RefCell;
use crate::view::ViewDelegate;
pub(crate) type CellFactoryMap = HashMap<&'static str, Box<Fn() -> Box<Any>>>;
#[derive(Clone)]
pub struct CellFactory(pub Rc<RefCell<CellFactoryMap>>);
impl std::fmt::Debug for CellFactory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CellFactory").finish()
}
}
impl CellFactory {
pub fn new() -> Self {
CellFactory(Rc::new(RefCell::new(HashMap::new())))
}
pub fn insert<F, T>(&self, identifier: &'static str, vendor: F)
where
F: Fn() -> T + 'static,
T: ViewDelegate + 'static
{
let mut lock = self.0.borrow_mut();
lock.insert(identifier, Box::new(move || {
let cell = vendor();
Box::new(cell) as Box<Any>
}));
}
pub fn get<R>(&self, identifier: &'static str) -> Box<R>
where
R: ViewDelegate + 'static
{
let lock = self.0.borrow();
let vendor = match lock.get(identifier) {
Some(v) => v,
None => {
panic!("Unable to dequeue cell of type {}: did you forget to register it?", identifier);
}
};
let view = vendor();
if let Ok(view) = view.downcast::<R>() {
view
} else {
panic!("Asking for cell of type {}, but failed to match the type!", identifier);
}
}
}
/// A helper method for instantiating view classes and applying default settings to them.
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
@ -110,6 +173,10 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Debug)]
pub struct ListView<T = ()> {
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell
/// allocation and reuse, which is necessary for an "infinite" listview.
cell_factory: CellFactory,
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
@ -176,6 +243,7 @@ impl ListView {
let anchor_view = view;
ListView {
cell_factory: CellFactory::new(),
delegate: None,
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
@ -193,19 +261,21 @@ impl ListView {
}
}
impl<T> ListView<T> where T: ListViewDelegate + 'static {
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> ListView<T> {
let mut delegate = Box::new(delegate);
let cell = CellFactory::new();
let view = allocate_view(register_listview_class_with_delegate::<T>);
unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let ptr: *const T = &*delegate;
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, ptr as usize);
let delegate_ptr: *const T = &*delegate;
let cell_vendor_ptr: *const RefCell<CellFactoryMap> = &*cell.0;
(&mut *view).set_ivar(LISTVIEW_DELEGATE_PTR, delegate_ptr as usize);
(&mut *view).set_ivar(LISTVIEW_CELL_VENDOR_PTR, cell_vendor_ptr as usize);
let _: () = msg_send![view, setDelegate:view];
let _: () = msg_send![view, setDataSource:view];
};
@ -229,6 +299,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
let anchor_view = view;
let mut view = ListView {
cell_factory: cell,
delegate: None,
top: LayoutAnchorY::new(unsafe { msg_send![anchor_view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![anchor_view, leadingAnchor] }),
@ -257,6 +328,7 @@ impl<T> ListView<T> {
/// delegate - the `View` is the only true holder of those.
pub(crate) fn clone_as_handle(&self) -> ListView {
ListView {
cell_factory: CellFactory::new(),
delegate: None,
top: self.top.clone(),
leading: self.leading.clone(),
@ -273,6 +345,34 @@ impl<T> ListView<T> {
}
}
/// Register a cell/row vendor function with an identifier. This is stored internally and used
/// for row-reuse.
pub fn register<F, R>(&self, identifier: &'static str, vendor: F)
where
F: Fn() -> R + 'static,
R: ViewDelegate + 'static
{
self.cell_factory.insert(identifier, vendor);
}
/// Dequeue a reusable cell. If one is not in the queue, will create and cache one for reuse.
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
#[cfg(target_os = "macos")]
unsafe {
let key = NSString::new(identifier).into_inner();
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:key owner:nil];
if cell != nil {
ListViewRow::from_cached(cell)
} else {
let delegate: Box<R> = self.cell_factory.get(identifier);
let view = ListViewRow::with_boxed(delegate);
view.set_identifier(identifier);
view
}
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color(&self, color: Color) {
let bg = color.into_platform_specific_color();
@ -296,7 +396,7 @@ impl<T> ListView<T> {
}
}
pub fn insert_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animations: ListViewAnimation) {
pub fn insert_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animation: RowAnimation) {
#[cfg(target_os = "macos")]
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
@ -306,12 +406,12 @@ impl<T> ListView<T> {
let _: () = msg_send![index_set, addIndex:x];
}
let animation_options: NSUInteger = animations.into();
let animation_options: NSUInteger = animation.into();
// We need to temporarily retain this; it can drop after the underlying NSTableView
// has also retained it.
let x = ShareId::from_ptr(index_set);
let _: () = msg_send![&*self.objc, insertRowsAtIndexes:&*x withAnimation:20];
let _: () = msg_send![&*self.objc, insertRowsAtIndexes:&*x withAnimation:animation_options];
}
}
@ -333,7 +433,7 @@ impl<T> ListView<T> {
}
}
pub fn remove_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animations: ListViewAnimation) {
pub fn remove_rows<I: IntoIterator<Item = usize>>(&self, indexes: I, animations: RowAnimation) {
#[cfg(target_os = "macos")]
unsafe {
let index_set: id = msg_send![class!(NSMutableIndexSet), new];
@ -348,7 +448,7 @@ impl<T> ListView<T> {
// We need to temporarily retain this; it can drop after the underlying NSTableView
// has also retained it.
let x = ShareId::from_ptr(index_set);
let _: () = msg_send![&*self.objc, removeRowsAtIndexes:&*x withAnimation:20];
let _: () = msg_send![&*self.objc, removeRowsAtIndexes:&*x withAnimation:animation_options];
}
}

View file

@ -11,7 +11,7 @@ use std::sync::Once;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id;
use crate::foundation::{id, YES, NO, NSUInteger};
@ -74,6 +74,21 @@ extern fn dragging_exited<T: ViewDelegate>(this: &mut Object, _: Sel, info: id)
});
}
/// Normally, you might not want to do a custom dealloc override. However, reusable cells are
/// tricky - since we "forget" them when we give them to the system, we need to make sure to do
/// proper cleanup then the backing (cached) version is deallocated on the Objective-C side. Since
/// we know
extern fn dealloc<T: ViewDelegate>(this: &Object, _: Sel) {
// Load the Box pointer here, and just let it drop normally.
unsafe {
let ptr: usize = *(&*this).get_ivar(LISTVIEW_ROW_DELEGATE_PTR);
let obj = ptr as *mut T;
let _x = Box::from_raw(obj);
let _: () = msg_send![super(this, class!(NSView)), dealloc];
}
}
/// Injects an `NSView` subclass. This is used for the default views that don't use delegates - we
/// have separate classes here since we don't want to waste cycles on methods that will never be
/// used if there's no delegates.
@ -116,6 +131,9 @@ pub(crate) fn register_listview_row_class_with_delegate<T: ViewDelegate>() -> *c
decl.add_method(sel!(concludeDragOperation:), conclude_drag_operation::<T> as extern fn (&mut Object, _, _));
decl.add_method(sel!(draggingExited:), dragging_exited::<T> as extern fn (&mut Object, _, _));
// Cleanup
decl.add_method(sel!(dealloc), dealloc::<T> as extern fn (&Object, _));
VIEW_CLASS = decl.register();
});

View file

@ -141,11 +141,49 @@ impl ListViewRow {
}
impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
/// When we're able to retrieve a reusable view cell from the backing table view, we can check
/// for the pointer and attempt to reconstruct the ListViewRow<T> that corresponds to this.
///
/// We can be reasonably sure that the pointer for the delegate is accurate, as:
///
/// - A `ListViewRow` is explicitly not clone-able
/// - It owns the Delegate on creation
/// - It takes ownership of the returned row in row_for_item
/// - When it takes ownership, it "forgets" the pointer - and the `dealloc` method on the
/// backing view cell will clean it up whenever it's dropped.
pub(crate) fn from_cached(view: id) -> ListViewRow<T> {
let delegate = unsafe {
let ptr: usize = *(&*view).get_ivar(LISTVIEW_ROW_DELEGATE_PTR);
let obj = ptr as *mut T;
Box::from_raw(obj)
//&*obj
};
//let delegate = crate::utils::load::<R>(&*view, LISTVIEW_ROW_DELEGATE_PTR);
let mut view = ListViewRow {
delegate: Some(delegate),
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
objc: unsafe { ShareId::from_ptr(view) },
};
view
}
pub fn with(delegate: T) -> ListViewRow<T> {
let delegate = Box::new(delegate);
Self::with_boxed(delegate)
}
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
/// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> ListViewRow<T> {
let mut delegate = Box::new(delegate);
pub fn with_boxed(mut delegate: Box<T>) -> ListViewRow<T> {
let view = allocate_view(register_listview_row_class_with_delegate::<T>);
unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
@ -171,13 +209,35 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
view.delegate = Some(delegate);
view
}
pub fn wut(mut self) -> ListViewRow {
// "forget" delegate, then move into standard ListViewRow
// to ease return type
let delegate = self.delegate.take();
if let Some(d) = delegate {
let _ = Box::into_raw(d);
}
ListViewRow {
delegate: None,
top: self.top.clone(),
leading: self.leading.clone(),
trailing: self.trailing.clone(),
bottom: self.bottom.clone(),
width: self.width.clone(),
height: self.height.clone(),
center_x: self.center_x.clone(),
center_y: self.center_y.clone(),
objc: self.objc.clone()
}
}
}
impl<T> From<&ListViewRow<T>> for ShareId<Object> {
/*impl<T> From<&ListViewRow<T>> for ShareId<Object> {
fn from(row: &ListViewRow<T>) -> ShareId<Object> {
row.objc.clone()
}
}
}*/
impl<T> ListViewRow<T> {
/// An internal method that returns a clone of this object, sans references to the delegate or
@ -199,6 +259,15 @@ impl<T> ListViewRow<T> {
}
}
/// Sets the identifier, which enables cells to be reused and dequeued properly.
pub fn set_identifier(&self, identifier: &'static str) {
let identifier = NSString::new(identifier).into_inner();
unsafe {
let _: () = msg_send![&*self.objc, setIdentifier:identifier];
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color(&self, color: Color) {
let bg = color.into_platform_specific_color();

View file

@ -2,7 +2,7 @@
use crate::Node;
use crate::dragdrop::{DragInfo, DragOperation};
use crate::listview::{ListView, ListViewRow};
use crate::listview::{ListView, ListViewRow, RowAction, RowEdge};
use crate::layout::Layout;
use crate::view::View;
@ -19,7 +19,11 @@ pub trait ListViewDelegate {
/// choose to try and work with this. NSTableView & such associated delegate patterns
/// are tricky to support in Rust, and while I have a few ideas about them, I haven't
/// had time to sit down and figure them out properly yet.
fn item(&self, _row: usize) -> Node;
fn item_for(&self, _row: usize) -> ListViewRow;
/// An optional delegate method; implement this if you'd like swipe-to-reveal to be
/// supported for a given row by returning a vector of actions to show.
fn actions_for(&self, row: usize, edge: RowEdge) -> Vec<RowAction> { Vec::new() }
/// Called when this is about to be added to the view heirarchy.
fn will_appear(&self, _animated: bool) {}

View file

@ -2,6 +2,8 @@
//! (think: drag and drop between applications). It exposes a Rust interface that tries to be
//! complete, but might not cover everything 100% right now - feel free to pull request.
use std::path::PathBuf;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc_id::ShareId;
@ -91,4 +93,33 @@ impl Pasteboard {
Ok(urls)
}
}
/// Looks inside the pasteboard contents and extracts what FileURLs are there, if any.
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::wrap(contents).map(|url| {
let path = NSString::wrap(msg_send![url, path]).to_str().to_string();
PathBuf::from(path)
}).into_iter().collect();
Ok(urls)
}
}
}

View file

@ -1,10 +1,10 @@
use std::path::Path;
use core_graphics::base::CGFloat;
use objc::runtime::{Object};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::ShareId;
use url::Url;
use crate::foundation::{id, YES, NSString, NSUInteger};
use crate::utils::CGSize;
@ -97,8 +97,8 @@ impl Default for ThumbnailConfig {
impl ThumbnailConfig {
/// Consumes the request and returns a native representation
/// (`QLThumbnailGenerationRequest`).
pub fn to_request(self, url: &Url) -> id {
let file = NSString::new(url.as_str());
pub fn to_request(self, path: &Path) -> id {
let file = NSString::new(path.to_str().unwrap());
let mut types: NSUInteger = 0;
for mask in self.types {
@ -108,8 +108,8 @@ impl ThumbnailConfig {
unsafe {
let size = CGSize::new(self.size.0, self.size.1);
let from_url: id = msg_send![class!(NSURL), URLWithString:file.into_inner()];
// @TODO: Check nil here, or other bad conversion
let from_url: id = msg_send![class!(NSURL), fileURLWithPath:file.into_inner()];
let request: id = msg_send![class!(QLThumbnailGenerationRequest), alloc];
let request: id = msg_send![request, initWithFileAtURL:from_url

View file

@ -1,11 +1,11 @@
use std::path::Path;
use objc::runtime::{Object};
use objc::{class, msg_send, sel, sel_impl};
use objc_id::ShareId;
use block::ConcreteBlock;
use url::Url;
use crate::error::Error;
use crate::foundation::{id, nil, NSUInteger};
use crate::image::Image;
@ -23,7 +23,7 @@ impl ThumbnailGenerator {
})
}
pub fn generate<F>(&self, url: &Url, config: ThumbnailConfig, callback: F)
pub fn generate<F>(&self, path: &Path, config: ThumbnailConfig, callback: F)
where
F: Fn(Result<(Image, ThumbnailQuality), Error>) + Send + Sync + 'static
{
@ -41,7 +41,7 @@ impl ThumbnailGenerator {
});
let block = block.copy();
let request = config.to_request(url);
let request = config.to_request(path);
unsafe {
let _: () = msg_send![&*self.0, generateRepresentationsForRequest:request

View file

@ -71,6 +71,10 @@ impl CGSize {
pub fn new(width: CGFloat, height: CGFloat) -> Self {
CGSize { width, height }
}
pub fn zero() -> Self {
CGSize { width: 0., height: 0. }
}
}
unsafe impl Encode for CGSize {