Rework the Layout trait implementation.

- Adds a new `ObjcProperty` type, which wraps our Objective-C objects in
  a Rust-backed Rc/Refcell combo. This in general makes understanding
  reference counting/storage more digestable when reading through the
  codebase and leans more on Rust to do its thing than the Objective-C
  runtime.

- Most widgets that need to implement `Layout` now only need to provide
  a slot for running a handler with the underlying node.

- Further documentation work ongoing.
This commit is contained in:
Ryan McGrath 2021-03-26 13:29:39 -07:00
parent 5748e76a97
commit 2f9a5b5e67
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
22 changed files with 598 additions and 592 deletions

View file

@ -1,9 +1,12 @@
//! Implements some stuff to handle dynamically setting the `NSBundle` identifier.
//! This is not currently in use, but does have places where it's useful... and to be honest I'm
//! kinda happy this is done as a swizzling implementation in pure Rust, which I couldn't find
//! examples of anywhere else.
//! Implements some functionality to handle dynamically setting the `NSBundle` identifier.
//!
//! Disregard until you can't, I guess.
//!
//
// This is not currently in use, but does have places where it's useful... and to be honest I'm
// kinda happy this is done as a swizzling implementation in pure Rust, which I couldn't find
// examples of anywhere else.
//
// Disregard until you can't, I guess.
use std::ffi::CString;
use std::mem;

94
src/button/enums.rs Normal file
View file

@ -0,0 +1,94 @@
use crate::foundation::NSUInteger;
/// Represents a bezel style for a button. This is a macOS-specific control, and has no effect
/// under iOS or tvOS.
#[cfg(feature = "macos")]
#[derive(Debug)]
pub enum BezelStyle {
/// A standard circular button.
Circular,
/// A standard disclosure style button.
Disclosure,
/// The standard looking "Help" (?) button.
HelpButton,
/// An inline style, varies across OS's.
Inline,
/// A recessed style, varies slightly across OS's.
Recessed,
/// A regular square style, with no special styling.
RegularSquare,
/// A standard rounded rectangle.
RoundRect,
/// A standard rounded button.
Rounded,
/// A standard rounded disclosure button.
RoundedDisclosure,
/// A shadowless square styl.e
ShadowlessSquare,
/// A small square style.
SmallSquare,
/// A textured rounded style.
TexturedRounded,
/// A textured square style.
TexturedSquare,
/// Any style that's not known by this framework (e.g, if Apple
/// introduces something new).
Unknown(NSUInteger)
}
#[cfg(feature = "macos")]
impl From<BezelStyle> for NSUInteger {
fn from(style: BezelStyle) -> Self {
match style {
BezelStyle::Circular => 7,
BezelStyle::Disclosure => 5,
BezelStyle::HelpButton => 9,
BezelStyle::Inline => 15,
BezelStyle::Recessed => 13,
BezelStyle::RegularSquare => 2,
BezelStyle::RoundRect => 12,
BezelStyle::Rounded => 1,
BezelStyle::RoundedDisclosure => 14,
BezelStyle::ShadowlessSquare => 6,
BezelStyle::SmallSquare => 10,
BezelStyle::TexturedRounded => 11,
BezelStyle::TexturedSquare => 8,
BezelStyle::Unknown(i) => i
}
}
}
#[cfg(feature = "macos")]
impl From<NSUInteger> for BezelStyle {
fn from(i: NSUInteger) -> Self {
match i {
7 => Self::Circular,
5 => Self::Disclosure,
9 => Self::HelpButton,
15 => Self::Inline,
13 => Self::Recessed,
2 => Self::RegularSquare,
12 => Self::RoundRect,
1 => Self::Rounded,
14 => Self::RoundedDisclosure,
6 => Self::ShadowlessSquare,
10 => Self::SmallSquare,
11 => Self::TexturedRounded,
8 => Self::TexturedSquare,
i => Self::Unknown(i)
}
}
}

View file

@ -1,10 +1,29 @@
//! A wrapper for NSButton. Currently the epitome of jank - if you're poking around here, expect
//! that this will change at some point.
//! Wraps `NSButton` on macOS, and `UIButton` on iOS and tvOS.
//!
//! You'd use this type to create a button that a user can interact with. Buttons can be configured
//! a number of ways, and support setting a callback to fire when they're clicked or tapped.
//!
//! Some properties are platform-specific; see the documentation for further information.
//!
//! ```rust,no_run
//! let mut button = Button::new("My button title");
//! button.set_text_equivalent("c");
//!
//! button.set_action(|| {
//! println!("My button was clicked.");
//! });
//!
//! // Make sure you don't let your Button drop for as long as you need it.
//! my_view.add_subview(&button);
//! ```
use std::fmt;
use std::sync::Once;
use objc_id::ShareId;
use std::cell::RefCell;
use std::rc::Rc;
use objc_id::Id;
use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl};
@ -15,17 +34,36 @@ use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger};
use crate::invoker::TargetActionHandler;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::text::{AttributedString, Font};
use crate::utils::load;
use crate::utils::{load, properties::ObjcProperty};
#[cfg(feature = "macos")]
use crate::macos::FocusRingType;
/// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSButton` lives.
mod enums;
pub use enums::*;
/// Wraps `NSButton` on macOS, and `UIButton` on iOS and tvOS.
///
/// You'd use this type to create a button that a user can interact with. Buttons can be configured
/// a number of ways, and support setting a callback to fire when they're clicked or tapped.
///
/// Some properties are platform-specific; see the documentation for further information.
///
/// ```rust,no_run
/// let mut button = Button::new("My button title");
/// button.set_text_equivalent("c");
///
/// button.set_action(|| {
/// println!("My button was clicked.");
/// });
///
/// // Make sure you don't let your Button drop for as long as you need it.
/// my_view.add_subview(&button);
/// ```
#[derive(Debug)]
pub struct Button {
/// A handle for the underlying Objective-C object.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A reference to an image, if set. We keep a copy to avoid any ownership snafus.
pub image: Option<Image>,
@ -93,34 +131,34 @@ impl Button {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
/// Sets an image on the underlying button.
pub fn set_image(&mut self, image: Image) {
unsafe {
let _: () = msg_send![&*self.objc, setImage:&*image.0];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setImage:&*image.0];
});
self.image = Some(image);
}
/// Sets the bezel style for this button.
/// Sets the bezel style for this button. Only supported on macOS.
#[cfg(feature = "macos")]
pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
let style: NSUInteger = bezel_style.into();
unsafe {
let _: () = msg_send![&*self.objc, setBezelStyle:style];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setBezelStyle:style];
});
}
/// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler);
//let handler = TargetActionHandler::new(&*self.objc, action);
//self.handler = Some(handler);
}
/// Call this to set the background color for the backing layer.
@ -128,10 +166,10 @@ impl Button {
let color: id = color.as_ref().into();
#[cfg(feature = "macos")]
unsafe {
let cell: id = msg_send![&*self.objc, cell];
self.objc.with_mut(|obj| unsafe {
let cell: id = msg_send![obj, cell];
let _: () = msg_send![cell, setBackgroundColor:color];
}
});
}
/// Set a key to be bound to this button. When the key is pressed, the action coupled to this
@ -139,9 +177,9 @@ impl Button {
pub fn set_key_equivalent(&self, key: &str) {
let key = NSString::new(key);
unsafe {
let _: () = msg_send![&*self.objc, setKeyEquivalent:&*key];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setKeyEquivalent:&*key];
});
}
/// Sets the text color for this button.
@ -149,94 +187,82 @@ impl Button {
/// On macOS, this is done by way of an `AttributedString` under the hood.
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
#[cfg(feature = "macos")]
unsafe {
let text: id = msg_send![&*self.objc, attributedTitle];
self.objc.with_mut(move |obj| unsafe {
let text: id = msg_send![obj, attributedTitle];
let len: isize = msg_send![text, length];
let mut attr_str = AttributedString::wrap(text);
attr_str.set_text_color(color, 0..len);
attr_str.set_text_color(color.as_ref(), 0..len);
let _: () = msg_send![&*self.objc, setAttributedTitle:&*attr_str];
}
let _: () = msg_send![obj, setAttributedTitle:&*attr_str];
});
}
// @TODO: Figure out how to handle oddities like this.
/// For buttons on macOS, one might need to disable the border. This does that.
#[cfg(feature = "macos")]
pub fn set_bordered(&self, is_bordered: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setBordered:match is_bordered {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setBordered:match is_bordered {
true => YES,
false => NO
}];
}
});
}
/// Sets the font for this button.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref().clone();
unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFont:&*font];
});
}
/// Sets how the control should draw a focus ring when a user is focused on it.
///
/// This is a macOS-only method.
#[cfg(feature = "macos")]
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
let ring_type: NSUInteger = focus_ring_type.into();
unsafe {
let _: () = msg_send![&*self.objc, setFocusRingType:ring_type];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFocusRingType:ring_type];
});
}
/// Toggles the highlighted status of the button.
pub fn set_highlighted(&self, highlight: bool) {
unsafe {
let _: () = msg_send![&*self.objc, highlight:match highlight {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, highlight:match highlight {
true => YES,
false => NO
}];
}
});
}
}
impl Layout for Button {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao.
If you think this should be supported, open a discussion on the GitHub repo.
"#);
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
impl Layout for &Button {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao.
If you think this should be supported, open a discussion on the GitHub repo.
"#);
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
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,
// but I'd rather be paranoid and remove them later.
fn drop(&mut self) {
unsafe {
let _: () = msg_send![&*self.objc, setTarget:nil];
let _: () = msg_send![&*self.objc, setAction:nil];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setTarget:nil];
let _: () = msg_send![obj, setAction:nil];
});
}
}
@ -254,96 +280,3 @@ fn register_class() -> *const Class {
unsafe { VIEW_CLASS }
}
/// Represents a bezel style for a button. This is a macOS-specific control, and has no effect
/// under iOS or tvOS.
#[cfg(feature = "macos")]
#[derive(Debug)]
pub enum BezelStyle {
/// A standard circular button.
Circular,
/// A standard disclosure style button.
Disclosure,
/// The standard looking "Help" (?) button.
HelpButton,
/// An inline style, varies across OS's.
Inline,
/// A recessed style, varies slightly across OS's.
Recessed,
/// A regular square style, with no special styling.
RegularSquare,
/// A standard rounded rectangle.
RoundRect,
/// A standard rounded button.
Rounded,
/// A standard rounded disclosure button.
RoundedDisclosure,
/// A shadowless square styl.e
ShadowlessSquare,
/// A small square style.
SmallSquare,
/// A textured rounded style.
TexturedRounded,
/// A textured square style.
TexturedSquare,
/// Any style that's not known by this framework (e.g, if Apple
/// introduces something new).
Unknown(NSUInteger)
}
#[cfg(feature = "macos")]
impl From<BezelStyle> for NSUInteger {
fn from(style: BezelStyle) -> Self {
match style {
BezelStyle::Circular => 7,
BezelStyle::Disclosure => 5,
BezelStyle::HelpButton => 9,
BezelStyle::Inline => 15,
BezelStyle::Recessed => 13,
BezelStyle::RegularSquare => 2,
BezelStyle::RoundRect => 12,
BezelStyle::Rounded => 1,
BezelStyle::RoundedDisclosure => 14,
BezelStyle::ShadowlessSquare => 6,
BezelStyle::SmallSquare => 10,
BezelStyle::TexturedRounded => 11,
BezelStyle::TexturedSquare => 8,
BezelStyle::Unknown(i) => i
}
}
}
#[cfg(feature = "macos")]
impl From<NSUInteger> for BezelStyle {
fn from(i: NSUInteger) -> Self {
match i {
7 => Self::Circular,
5 => Self::Disclosure,
9 => Self::HelpButton,
15 => Self::Inline,
13 => Self::Recessed,
2 => Self::RegularSquare,
12 => Self::RoundRect,
1 => Self::Rounded,
14 => Self::RoundedDisclosure,
6 => Self::ShadowlessSquare,
10 => Self::SmallSquare,
11 => Self::TexturedRounded,
8 => Self::TexturedSquare,
i => Self::Unknown(i)
}
}
}

View file

@ -27,7 +27,6 @@ pub(crate) const AQUA_LIGHT_COLOR_HIGH_CONTRAST: &'static str = "AQUA_LIGHT_COLO
pub(crate) const AQUA_DARK_COLOR_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
extern "C" {
static NSAppearanceNameAqua: id;
static NSAppearanceNameAccessibilityHighContrastAqua: id;

View file

@ -2,7 +2,8 @@
use core_graphics::geometry::{CGRect, CGPoint, CGSize};
/// A struct that represents a box - top, left, width and height.
/// A struct that represents a box - top, left, width and height. You might use this for, say,
/// setting the initial frame of a view.
#[derive(Copy, Clone, Debug)]
pub struct Rect {
/// Distance from the top, in points.

View file

@ -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, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -43,7 +44,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Clone, Debug)]
pub struct ImageView {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
@ -98,47 +99,38 @@ impl ImageView {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let cg = color.as_ref().cg_color();
unsafe {
let layer: id = msg_send![&*self.objc, layer];
self.objc.with_mut(|obj| unsafe {
let cg = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor:cg];
}
});
}
pub fn set_image(&self, image: &Image) {
unsafe {
let _: () = msg_send![&*self.objc, setImage:&*image.0];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setImage:&*image.0];
});
}
pub fn set_hidden(&self, hidden: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setHidden:match hidden {
true => YES,
false => NO
}];
}
});
}
}
impl Layout for ImageView {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}

View file

@ -48,6 +48,7 @@ use crate::color::Color;
use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES};
use crate::layout::{Layout, LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
use crate::text::{Font, TextAlign};
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -85,7 +86,7 @@ fn common_init(class: *const Class) -> id {
#[derive(Debug)]
pub struct TextField<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>,
@ -145,7 +146,7 @@ impl TextField {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
}
@ -178,7 +179,7 @@ where
height: LayoutAnchorDimension::height(label),
center_x: LayoutAnchorX::center(label),
center_y: LayoutAnchorY::center(label),
objc: unsafe { ShareId::from_ptr(label) },
objc: ObjcProperty::retain(label),
};
(&mut delegate).did_load(label.clone_as_handle());
@ -211,61 +212,50 @@ impl<T> TextField<T> {
/// Grabs the value from the textfield and returns it as an owned String.
pub fn get_value(&self) -> String {
let value = NSString::retain(unsafe {
msg_send![&*self.objc, stringValue]
});
value.to_string()
self.objc.get(|obj| unsafe {
NSString::retain(msg_send![obj, stringValue]).to_string()
})
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let cg = color.as_ref().cg_color();
unsafe {
let layer: id = msg_send![&*self.objc, layer];
self.objc.with_mut(|obj| unsafe {
let cg = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor: cg];
}
});
}
/// Call this to set the text for the label.
pub fn set_text(&self, text: &str) {
let s = NSString::new(text);
unsafe {
let _: () = msg_send![&*self.objc, setStringValue:&*s];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setStringValue:&*s];
});
}
/// The the text alignment style for this control.
pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe {
self.objc.with_mut(|obj| unsafe {
let alignment: NSInteger = alignment.into();
let _: () = msg_send![&*self.objc, setAlignment: alignment];
}
let _: () = msg_send![obj, setAlignment: alignment];
});
}
/// Sets the font for this input.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref().clone();
unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFont:&*font];
});
}
}
impl<T> Layout for TextField<T> {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview: backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
@ -277,13 +267,13 @@ impl<T> Drop for TextField<T> {
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
if self.delegate.is_some() {
/*if self.delegate.is_some() {
unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}
}*/
}
}

View file

@ -16,25 +16,22 @@ pub trait Layout {
/// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep
/// the general lazy-loading approach Cocoa has. This may change in the future, and in general
/// this shouldn't affect your code too much (if at all).
fn get_backing_node(&self) -> ShareId<Object>;
fn with_backing_node<F: Fn(id)>(&self, handler: F);
/// Adds another Layout-backed control or view as a subview of this view.
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = self.get_backing_node();
let subview_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*backing_node, addSubview:&*subview_node];
}
self.with_backing_node(|backing_node| {
view.with_backing_node(|subview_node| unsafe {
let _: () = msg_send![backing_node, addSubview:subview_node];
});
});
}
/// Removes a control or view from the superview.
fn remove_from_superview(&self) {
let backing_node = self.get_backing_node();
unsafe {
let _: () = msg_send![&*backing_node, removeFromSuperview];
}
self.with_backing_node(|backing_node| unsafe {
let _: () = msg_send![backing_node, removeFromSuperview];
});
}
/// Sets the `frame` for the view this trait is applied to.
@ -43,12 +40,11 @@ pub trait Layout {
/// `set_translates_autoresizing_mask_into_constraints` to enable frame-based layout calls (or
/// use an appropriate initializer for a given view type).
fn set_frame<R: Into<CGRect>>(&self, rect: R) {
let backing_node = self.get_backing_node();
let frame: CGRect = rect.into();
unsafe {
let _: () = msg_send![&*backing_node, setFrame:frame];
}
self.with_backing_node(move |backing_node| unsafe {
let _: () = msg_send![backing_node, setFrame:frame];
});
}
/// Sets whether the view for this trait should translate autoresizing masks into layout
@ -57,13 +53,11 @@ pub trait Layout {
/// Cacao defaults this to `false`; if you need to set frame-based layout pieces,
/// then you should set this to `true` (or use an appropriate initializer that does it for you).
fn set_translates_autoresizing_mask_into_constraints(&self, translates: bool) {
let backing_node = self.get_backing_node();
unsafe {
let _: () = msg_send![&*backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
self.with_backing_node(|backing_node| unsafe {
let _: () = msg_send![backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
true => YES,
false => NO
}];
}
});
}
}

View file

@ -52,10 +52,9 @@ extern fn view_for_column<T: ListViewDelegate>(
//
// @TODO: Finish investing the `Rc` approach, might be able to just take
// ownership and rely on Rust being correct.
let objc = item.objc.borrow();
unsafe {
msg_send![&**objc, self]
}
item.objc.get(|obj| unsafe {
msg_send![obj, self]
})
}
extern fn will_display_cell<T: ListViewDelegate>(

View file

@ -54,6 +54,7 @@ use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}
use crate::pasteboard::PasteboardType;
use crate::scrollview::ScrollView;
use crate::utils::{os, CellFactory, CGSize};
use crate::utils::properties::{ObjcProperty, PropertyNullable};
use crate::view::ViewDelegate;
#[cfg(target_os = "macos")]
@ -126,55 +127,6 @@ fn common_init(class: *const Class) -> id {
}
}
use objc_id::Id;
#[derive(Clone, Debug)]
pub struct ObjcProperty(Rc<RefCell<Id<Object>>>);
impl ObjcProperty {
pub fn new(obj: id) -> Self {
Self(Rc::new(RefCell::new(unsafe {
Id::from_ptr(obj)
})))
}
pub fn with<F>(&self, handler: F)
where
F: Fn(&Object)
{
let borrow = self.0.borrow();
handler(&borrow);
}
}
#[derive(Debug, Default)]
pub struct PropertyNullable<T>(Rc<RefCell<Option<T>>>);
impl<T> PropertyNullable<T> {
pub fn new(obj: T) -> Self {
Self(Rc::new(RefCell::new(Some(obj))))
}
pub fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
pub fn with<F>(&self, handler: F)
where
F: Fn(&T)
{
let borrow = self.0.borrow();
if let Some(s) = &*borrow {
handler(s);
}
}
pub fn set(&self, obj: T) {
let mut borrow = self.0.borrow_mut();
*borrow = Some(obj);
}
}
#[derive(Debug)]
pub struct ListView<T = ()> {
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell
@ -184,7 +136,7 @@ pub struct ListView<T = ()> {
menu: PropertyNullable<Vec<MenuItem>>,
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// On macOS, we need to manage the NSScrollView ourselves. It's a bit
/// more old school like that...
@ -241,9 +193,9 @@ impl ListView {
let scrollview = {
let sview = ScrollView::new();
unsafe {
let _: () = msg_send![&*sview.objc, setDocumentView:view];
}
sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setDocumentView:view];
});
sview
};
@ -251,7 +203,9 @@ impl ListView {
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
// @TODO: Fix this with proper mutable access.
#[cfg(target_os = "macos")]
let anchor_view: id = unsafe { msg_send![&*scrollview.objc, self] };
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
msg_send![obj, self]
});
#[cfg(target_os = "ios")]
let anchor_view: id = view;
@ -270,7 +224,7 @@ impl ListView {
height: LayoutAnchorDimension::height(anchor_view),
center_x: LayoutAnchorX::center(anchor_view),
center_y: LayoutAnchorY::center(anchor_view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
#[cfg(target_os = "macos")]
scrollview: scrollview
@ -300,16 +254,18 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
let scrollview = {
let sview = ScrollView::new();
unsafe {
let _: () = msg_send![&*sview.objc, setDocumentView:view];
}
sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setDocumentView:view];
});
sview
};
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
#[cfg(target_os = "macos")]
let anchor_view: id = unsafe { msg_send![&*scrollview.objc, self] };
let anchor_view: id = scrollview.objc.get(|obj| unsafe {
msg_send![obj, self]
});
#[cfg(target_os = "ios")]
let anchor_view = view;
@ -328,7 +284,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
height: LayoutAnchorDimension::height(anchor_view),
center_x: LayoutAnchorX::center(anchor_view),
center_y: LayoutAnchorY::center(anchor_view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
#[cfg(target_os = "macos")]
scrollview: scrollview
@ -382,7 +338,9 @@ impl<T> ListView<T> {
#[cfg(target_os = "macos")]
{
let key = NSString::new(identifier);
let cell: id = unsafe { msg_send![&*self.objc, makeViewWithIdentifier:&*key owner:nil] };
let cell: id = self.objc.get(|obj| unsafe {
msg_send![obj, makeViewWithIdentifier:&*key owner:nil]
});
if cell != nil {
ListViewRow::from_cached(cell)
@ -398,12 +356,11 @@ impl<T> ListView<T> {
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
// @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe {
let layer: id = msg_send![&*self.objc, layer];
self.objc.with_mut(|obj| unsafe {
let color = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor:color];
}
});
}
/// Sets the style for the underlying NSTableView. This property is only supported on macOS
@ -411,9 +368,9 @@ impl<T> ListView<T> {
#[cfg(feature = "macos")]
pub fn set_style(&self, style: crate::foundation::NSInteger) {
if os::is_minimum_version(11) {
unsafe {
let _: () = msg_send![&*self.objc, setStyle:style];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setStyle:style];
});
}
}
@ -424,19 +381,19 @@ impl<T> ListView<T> {
/// view for navigation purposes.
#[cfg(feature = "macos")]
pub fn set_allows_empty_selection(&self, allows: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setAllowsEmptySelection:match allows {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setAllowsEmptySelection:match allows {
true => YES,
false => NO
}];
}
});
}
/// Set the selection highlight style.
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
unsafe {
let _: () = msg_send![&*self.objc, setSelectionHighlightStyle:style];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setSelectionHighlightStyle:style];
});
}
/// Select the rows at the specified indexes, optionally adding to any existing selections.
@ -448,10 +405,12 @@ impl<T> ListView<T> {
let _: () = msg_send![index_set, addIndex:index];
}
let _: () = msg_send![&*self.objc, selectRowIndexes:index_set byExtendingSelection:match extends_existing {
true => YES,
false => NO
}];
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, selectRowIndexes:index_set byExtendingSelection:match extends_existing {
true => YES,
false => NO
}];
});
}
}
@ -466,14 +425,19 @@ impl<T> ListView<T> {
/// });
/// ```
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) {
// Note that we need to thread the `with_mut` calls carefully, to avoid deadlocking.
#[cfg(target_os = "macos")]
unsafe {
let _: () = msg_send![&*self.objc, beginUpdates];
{
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, beginUpdates];
});
let handle = self.clone_as_handle();
update(handle);
let _: () = msg_send![&*self.objc, endUpdates];
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, endUpdates];
});
}
}
@ -497,7 +461,9 @@ 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, insertRowsAtIndexes:&*x withAnimation:animation_options];
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, insertRowsAtIndexes:&*x withAnimation:animation_options];
});
}
}
@ -516,7 +482,10 @@ impl<T> ListView<T> {
let ye: id = msg_send![class!(NSIndexSet), indexSetWithIndex:0];
let y = ShareId::from_ptr(ye);
let _: () = msg_send![&*self.objc, reloadDataForRowIndexes:&*x columnIndexes:&*y];
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, reloadDataForRowIndexes:&*x columnIndexes:&*y];
});
}
}
@ -540,16 +509,18 @@ 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:animation_options];
self.objc.with_mut(|obj| {
let _: () = msg_send![obj, removeRowsAtIndexes:&*x withAnimation:animation_options];
});
}
}
/// Sets an enforced row-height; if you need dynamic rows, you'll want to
/// look at ListViewDelegate methods, or use AutoLayout.
pub fn set_row_height(&self, height: CGFloat) {
unsafe {
let _: () = msg_send![&*self.objc, setRowHeight:height];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setRowHeight:height];
});
}
/// This defaults to true. If you're using manual heights, you may want to set this to `false`,
@ -559,12 +530,12 @@ impl<T> ListView<T> {
/// It can make some scrolling situations much smoother.
pub fn set_uses_automatic_row_heights(&self, uses: bool) {
#[cfg(target_os = "macos")]
unsafe {
let _: () = msg_send![&*self.objc, setUsesAutomaticRowHeights:match uses {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setUsesAutomaticRowHeights:match uses {
true => YES,
false => NO
}];
}
});
}
/// On macOS, this will instruct the underlying NSTableView to alternate
@ -572,37 +543,37 @@ impl<T> ListView<T> {
/// to hard-set a row height as well.
pub fn set_uses_alternating_backgrounds(&self, uses: bool) {
#[cfg(target_os = "macos")]
unsafe {
let _: () = msg_send![&*self.objc, setUsesAlternatingRowBackgroundColors:match uses {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setUsesAlternatingRowBackgroundColors:match uses {
true => YES,
false => NO
}];
}
});
}
/// End actions for a row. API subject to change.
pub fn set_row_actions_visible(&self, visible: bool) {
#[cfg(target_os = "macos")]
unsafe {
let _: () = msg_send![&*self.objc, setRowActionsVisible:match visible {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setRowActionsVisible:match visible {
true => YES,
false => NO
}];
}
});
}
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let _: () = msg_send![&*self.objc, registerForDraggedTypes:&*types];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
@ -611,14 +582,14 @@ impl<T> ListView<T> {
/// Calling this will reload (and redraw) your listview based on whatever the data source
/// reports back.
pub fn reload(&self) {
unsafe {
let _: () = msg_send![&*self.objc, reloadData];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, reloadData];
});
}
/// Returns the selected row.
pub fn get_selected_row_index(&self) -> NSInteger {
unsafe { msg_send![&*self.objc, selectedRow] }
self.objc.get(|obj| unsafe { msg_send![obj, selectedRow] })
}
/// Returns the currently clicked row. This is macOS-specific, and is generally used in context
@ -643,32 +614,13 @@ impl<T> ListView<T> {
/// }
/// ```
pub fn get_clicked_row_index(&self) -> NSInteger {
unsafe { msg_send![&*self.objc, clickedRow] }
self.objc.get(|obj| unsafe { msg_send![obj, clickedRow] })
}
}
impl<T> Layout for ListView<T> {
/// On macOS, this returns the NSScrollView, not the NSTableView.
fn get_backing_node(&self) -> ShareId<Object> {
#[cfg(target_os = "macos")]
let val = self.scrollview.objc.clone();
#[cfg(target_os = "ios")]
let val = self.objc.clone();
val
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
#[cfg(target_os = "macos")]
let _: () = msg_send![&*self.scrollview.objc, addSubview:backing_node];
#[cfg(target_os = "ios")]
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
@ -680,13 +632,13 @@ impl<T> Drop for ListView<T> {
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
if self.delegate.is_some() {
/*if self.delegate.is_some() {
unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}
}*/
}
}

View file

@ -53,6 +53,7 @@ use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::view::ViewDelegate;
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -88,7 +89,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Debug)]
pub struct ListViewRow<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: Rc<RefCell<Id<Object>>>,
pub objc: ObjcProperty,
/// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>,
@ -147,7 +148,7 @@ impl ListViewRow {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
objc: ObjcProperty::retain(view),
}
}
}
@ -184,7 +185,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
objc: ObjcProperty::retain(view),
};
view
@ -218,7 +219,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
objc: ObjcProperty::retain(view),
};
(&mut delegate).did_load(view.clone_as_handle());
@ -246,17 +247,11 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
height: self.height.clone(),
center_x: self.center_x.clone(),
center_y: self.center_y.clone(),
objc: Rc::clone(&self.objc)
objc: self.objc.clone()
}
}
}
/*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
/// callback pointer. We use this in calling `did_load()` - implementing delegates get a way to
@ -275,7 +270,7 @@ impl<T> ListViewRow<T> {
height: self.height.clone(),
center_x: self.center_x.clone(),
center_y: self.center_y.clone(),
objc: Rc::clone(&self.objc)
objc: self.objc.clone()
}
}
@ -283,56 +278,38 @@ impl<T> ListViewRow<T> {
pub fn set_identifier(&self, identifier: &'static str) {
let identifier = NSString::new(identifier);
let objc = self.objc.borrow();
unsafe {
let _: () = msg_send![&**objc, setIdentifier:&*identifier];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setIdentifier:&*identifier];
});
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let mut objc = self.objc.borrow_mut();
let color: id = color.as_ref().into();
unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color);
}
self.objc.with_mut(|obj| unsafe {
(&mut *obj).set_ivar(BACKGROUND_COLOR, color);
});
}
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let objc = self.objc.borrow();
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
}
impl<T> Layout for ListViewRow<T> {
fn get_backing_node(&self) -> ShareId<Object> {
let objc = self.objc.borrow();
unsafe {
// @TODO: Need a better solution here.
let x: id = msg_send![&**objc, self];
ShareId::from_ptr(x)
}
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
let objc = self.objc.borrow();
unsafe {
let _: () = msg_send![&**objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}

View file

@ -69,9 +69,9 @@ impl ToolbarItem {
pub fn set_button(&mut self, button: Button) {
button.set_bezel_style(BezelStyle::TexturedRounded);
unsafe {
let _: () = msg_send![&*self.objc, setView:&*button.objc];
}
button.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setView:obj];
});
self.button = Some(button);
}

View file

@ -289,11 +289,9 @@ 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) {
let backing_node = view.get_backing_node();
unsafe {
view.with_backing_node(|backing_node| unsafe {
let _: () = msg_send![&*self.objc, setContentView:&*backing_node];
}
});
}
/// Given a view, sets it as the content view controller for this window.

View file

@ -1,6 +1,3 @@
//! This module provides some basic wrappers for Pasteboard functionality. It's currently not an
//! exhaustive clone, but feel free to pull request accordingly!
use crate::foundation::NSString;
/// Constants for the standard system pasteboard names.

View file

@ -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, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "ios")]
mod ios;
@ -34,7 +35,7 @@ pub use enums::ProgressIndicatorStyle;
#[derive(Debug)]
pub struct ProgressIndicator {
/// A pointer to the Objective-C Object.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A pointer to the Objective-C runtime top layout constraint.
pub top: LayoutAnchorY,
@ -99,7 +100,7 @@ impl ProgressIndicator {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
}
@ -107,32 +108,33 @@ impl ProgressIndicator {
impl ProgressIndicator {
/// Starts the animation for an indeterminate indicator.
pub fn start_animation(&self) {
unsafe {
let _: () = msg_send![&*self.objc, startAnimation:nil];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, startAnimation:nil];
});
}
/// Stops any animations that are currently happening on this indicator (e.g, if it's an
/// indeterminate looping animation).
pub fn stop_animation(&self) {
unsafe {
let _: () = msg_send![&*self.objc, stopAnimation:nil];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, stopAnimation:nil];
});
}
/// Increment the progress indicator by the amount specified.
pub fn increment(&self, amount: f64) {
unsafe {
let _: () = msg_send![&*self.objc, incrementBy:amount];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, incrementBy:amount];
});
}
/// Set the style for the progress indicator.
pub fn set_style(&self, style: ProgressIndicatorStyle) {
unsafe {
let style = style as NSUInteger;
let _: () = msg_send![&*self.objc, setStyle:style];
}
let style = style as NSUInteger;
self.objc.with_mut(move |obj| unsafe {
let _: () = msg_send![obj, setStyle:style];
});
}
/// Set whether this is an indeterminate indicator or not. Indeterminate indicators are
@ -140,12 +142,12 @@ impl ProgressIndicator {
///
/// Invert this to go back to a bar appearance.
pub fn set_indeterminate(&self, is_indeterminate: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setIndeterminate:match is_indeterminate {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setIndeterminate:match is_indeterminate {
true => YES,
false => NO
}];
}
});
}
/// Sets the value of this progress indicator.
@ -154,33 +156,25 @@ impl ProgressIndicator {
pub fn set_value(&self, value: f64) {
let value = value as CGFloat;
unsafe {
let _: () = msg_send![&*self.objc, setDoubleValue:value];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setDoubleValue:value];
});
}
/// Set whether this control is hidden or not.
pub fn set_hidden(&self, hidden: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setHidden:match hidden {
true => YES,
false => NO
}];
}
});
}
}
impl Layout for ProgressIndicator {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
@ -193,11 +187,11 @@ impl Drop for ProgressIndicator {
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
unsafe {
/*unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}*/
}
}

View file

@ -49,6 +49,7 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -90,7 +91,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Debug)]
pub struct ScrollView<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>,
@ -149,7 +150,7 @@ impl ScrollView {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
}
@ -178,7 +179,7 @@ impl<T> ScrollView<T> where T: ScrollViewDelegate + 'static {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
};
(&mut delegate).did_load(view.clone_as_handle());
@ -212,26 +213,17 @@ impl<T> ScrollView<T> {
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
// @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe {
let layer: id = msg_send![&*self.objc, layer];
self.objc.with_mut(|obj| unsafe {
let color = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor:color];
}
});
}
}
impl<T> Layout for ScrollView<T> {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
@ -243,13 +235,13 @@ impl<T> Drop for ScrollView<T> {
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
if self.delegate.is_some() {
/*if self.delegate.is_some() {
unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}
}*/
}
}

View file

@ -12,14 +12,14 @@ 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, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::utils::load;
use crate::utils::{load, properties::ObjcProperty};
/// A wrapper for `NSSwitch`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSSwitch` lives.
#[derive(Debug)]
pub struct Switch {
/// A pointer to the underlying Objective-C Object.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
handler: Option<TargetActionHandler>,
/// A pointer to the Objective-C runtime top layout constraint.
@ -79,35 +79,35 @@ impl Switch {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
/// Sets whether this is checked on or off.
pub fn set_checked(&mut self, checked: bool) {
unsafe {
self.objc.with_mut(|obj| unsafe {
// @TODO: The constants to use here changed back in 10.13ish, so... do we support that,
// or just hide it?
let _: () = msg_send![&*self.objc, setState:match checked {
let _: () = msg_send![obj, setState:match checked {
true => 1,
false => 0
}];
}
});
}
/// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler);
//let handler = TargetActionHandler::new(&*self.objc, action);
//self.handler = Some(handler);
}
}
impl Layout for Switch {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao. If you think this should be supported,
@ -120,10 +120,10 @@ impl Drop for Switch {
// 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) {
unsafe {
let _: () = msg_send![&*self.objc, setTarget:nil];
let _: () = msg_send![&*self.objc, setAction:nil];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setTarget:nil];
let _: () = msg_send![obj, setAction:nil];
});
}
}

View file

@ -48,6 +48,7 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSInteger, NSUInteger, NSStri
use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::text::{Font, TextAlign, LineBreakMode};
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -90,10 +91,51 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
/// A clone-able handler to an `NSTextField/UILabel` reference in the
/// Objective-C runtime.
/// Wraps `NSTextField` and `UILabel` across platforms, explicitly as a Label.
/// In AppKit, `NSTextField` does double duty, and for clarity we just double
/// the implementation.
///
/// Labels implement Autolayout, which enable you to specify how things should appear on the screen.
///
/// ```rust,no_run
/// use cacao::color::rgb;
/// use cacao::layout::{Layout, LayoutConstraint};
/// use cacao::view::Label;
/// use cacao::window::{Window, WindowDelegate};
///
/// #[derive(Default)]
/// struct AppWindow {
/// content: Label,
/// label: Label,
/// window: Window
/// }
///
/// impl WindowDelegate for AppWindow {
/// fn did_load(&mut self, window: Window) {
/// window.set_minimum_content_size(300., 300.);
/// self.window = window;
///
/// self.label.set_background_color(rgb(224, 82, 99));
/// self.label.set_text("LOL");
/// self.content.add_subview(&self.red);
///
/// self.window.set_content_view(&self.content);
///
/// LayoutConstraint::activate(&[
/// self.red.top.constraint_equal_to(&self.content.top).offset(16.),
/// self.red.leading.constraint_equal_to(&self.content.leading).offset(16.),
/// self.red.trailing.constraint_equal_to(&self.content.trailing).offset(-16.),
/// self.red.bottom.constraint_equal_to(&self.content.bottom).offset(-16.),
/// ]);
/// }
/// }
/// ```
///
/// For more information on Autolayout, view the module or check out the examples folder.
#[derive(Debug)]
pub struct Label<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>,
pub objc: ObjcProperty,
/// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>,
@ -152,7 +194,7 @@ impl Label {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) },
objc: ObjcProperty::retain(view),
}
}
}
@ -183,7 +225,7 @@ impl<T> Label<T> where T: LabelDelegate + 'static {
height: LayoutAnchorDimension::height(label),
center_x: LayoutAnchorX::center(label),
center_y: LayoutAnchorY::center(label),
objc: unsafe { ShareId::from_ptr(label) },
objc: ObjcProperty::retain(label),
};
//(&mut delegate).did_load(label.clone_as_handle());
@ -217,21 +259,21 @@ impl<T> Label<T> {
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
// @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe {
let layer: id = msg_send![&*self.objc, layer];
// Needs to set ivar and such, akin to View.
self.objc.with_mut(|obj| unsafe {
let color = color.as_ref().cg_color();
let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor:color];
}
});
}
/// Call this to set the color of the text.
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
let color: id = color.as_ref().into();
unsafe {
let _: () = msg_send![&*self.objc, setTextColor:color];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setTextColor:color];
});
}
/// Call this to set the text for the label.
@ -239,26 +281,24 @@ impl<T> Label<T> {
let text = text.as_ref();
let s = NSString::new(text);
unsafe {
let _: () = msg_send![&*self.objc, setStringValue:&*s];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setStringValue:&*s];
});
}
/// Retrieve the text currently held in the label.
pub fn get_text(&self) -> String {
let s = NSString::retain(unsafe {
msg_send![&*self.objc, stringValue]
});
s.to_string()
self.objc.get(|obj| unsafe {
NSString::retain(msg_send![obj, stringValue]).to_string()
})
}
/// Sets the text alignment for this label.
pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe {
self.objc.with_mut(|obj| unsafe {
let alignment: NSInteger = alignment.into();
let _: () = msg_send![&*self.objc, setAlignment:alignment];
}
let _: () = msg_send![obj, setAlignment:alignment];
});
}
/// Sets the font for this label.
@ -267,51 +307,43 @@ impl<T> Label<T> {
// font object - it seems like it can be optimized away otherwise.
let font = font.as_ref().clone();
unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setFont:&*font];
});
}
/// Set whether this is hidden or not.
pub fn set_hidden(&self, hidden: bool) {
unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setHidden:match hidden {
true => YES,
false => NO
}];
}
});
}
/// Sets the maximum number of lines.
pub fn set_max_number_of_lines(&self, num: NSInteger) {
unsafe {
let _: () = msg_send![&*self.objc, setMaximumNumberOfLines:num];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setMaximumNumberOfLines:num];
});
}
/// Set the line break mode for this label.
pub fn set_line_break_mode(&self, mode: LineBreakMode) {
#[cfg(target_os = "macos")]
unsafe {
let cell: id = msg_send![&*self.objc, cell];
self.objc.with_mut(|obj| unsafe {
let cell: id = msg_send![obj, cell];
let mode = mode as NSUInteger;
let _: () = msg_send![cell, setTruncatesLastVisibleLine:YES];
let _: () = msg_send![cell, setLineBreakMode:mode];
}
});
}
}
impl<T> Layout for Label<T> {
fn get_backing_node(&self) -> ShareId<Object> {
self.objc.clone()
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}
@ -323,13 +355,13 @@ impl<T> Drop for Label<T> {
///
/// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) {
if self.delegate.is_some() {
/*if self.delegate.is_some() {
unsafe {
let superview: id = msg_send![&*self.objc, superview];
if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview];
}
}
}
}*/
}
}

View file

@ -16,6 +16,7 @@ mod cell_factory;
pub use cell_factory::CellFactory;
pub mod os;
pub mod properties;
/// A generic trait that's used throughout multiple different controls in this framework - acts as
/// a guard for whether something is a (View|Window|etc)Controller.

74
src/utils/properties.rs Normal file
View file

@ -0,0 +1,74 @@
use std::cell::RefCell;
use std::rc::Rc;
use objc_id::Id;
use objc::runtime::Object;
use crate::foundation::id;
/// A wrapper for single-threaded `ObjcProperty` types.
///
/// An `ObjcProperty` is something that exists on the Objective-C side that we want to interact with, and
/// support cloning with respect to our side and the general Rust rules. Thus, we do a layer of
/// Rc/RefCell to shield things and make life easier.
///
/// It is possible we could remove the `Id` wrapper in here if we're just doing this ourselves, and
/// is probably worth investigating at some point.
#[derive(Clone, Debug)]
pub struct ObjcProperty(Rc<RefCell<Id<Object>>>);
impl ObjcProperty {
/// Given an Objective-C object, retains it and wraps it as a `Property`.
pub fn retain(obj: id) -> Self {
ObjcProperty(Rc::new(RefCell::new(unsafe {
Id::from_ptr(obj)
})))
}
/// Runs a handler with mutable access for the underlying Objective-C object.
///
/// Note that this is mutable access from the Rust side; we make every effort to ensure things are valid
/// on the Objective-C side as well, but there be dragons.
pub fn with_mut<F: Fn(id)>(&self, handler: F) {
let mut obj = self.0.borrow_mut();
handler(&mut **obj);
}
/// Runs a handler with the underlying Objective-C type.
///
/// The handler can return whatever; this is primarily intended for dynamically calling getters
/// on the underlying type.
pub fn get<R, F: Fn(&Object) -> R>(&self, handler: F) -> R {
let obj = self.0.borrow();
handler(&**obj)
}
}
/// A wrapper for a single-threaded nullable `Property`.
#[derive(Debug, Default)]
pub struct PropertyNullable<T>(Rc<RefCell<Option<T>>>);
impl<T> PropertyNullable<T> {
pub fn new(obj: T) -> Self {
Self(Rc::new(RefCell::new(Some(obj))))
}
pub fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
pub fn with<F>(&self, handler: F)
where
F: Fn(&T)
{
let borrow = self.0.borrow();
if let Some(s) = &*borrow {
handler(s);
}
}
pub fn set(&self, obj: T) {
let mut borrow = self.0.borrow_mut();
*borrow = Some(obj);
}
}

View file

@ -63,7 +63,9 @@ where
(&mut *vc).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
}
let _: () = msg_send![vc, setView:&*view.get_backing_node()];
view.with_backing_node(|backing_node| {
let _: () = msg_send![vc, setView:backing_node];
});
ShareId::from_ptr(vc)
};

View file

@ -49,9 +49,7 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use std::rc::Rc;
use std::cell::RefCell;
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")]
mod macos;
@ -96,7 +94,7 @@ fn common_init(class: *const Class) -> id {
#[derive(Debug)]
pub struct View<T = ()> {
/// A pointer to the Objective-C runtime view controller.
pub objc: Rc<RefCell<Id<Object>>>,
pub objc: ObjcProperty,
/// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>,
@ -155,7 +153,7 @@ impl View {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
objc: ObjcProperty::retain(view),
}
}
}
@ -187,7 +185,7 @@ impl<T> View<T> where T: ViewDelegate + 'static {
height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })),
objc: ObjcProperty::retain(view),
};
(&mut delegate).did_load(view.clone_as_handle());
@ -214,51 +212,35 @@ impl<T> View<T> {
height: self.height.clone(),
center_x: self.center_x.clone(),
center_y: self.center_y.clone(),
objc: Rc::clone(&self.objc)
objc: self.objc.clone()
}
}
/// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let mut objc = self.objc.borrow_mut();
let color: id = color.as_ref().into();
unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color);
}
self.objc.with_mut(|obj| unsafe {
(&mut *obj).set_ivar(BACKGROUND_COLOR, color);
});
}
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe {
let types: NSArray = types.into_iter().map(|t| {
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let types: NSArray = types.into_iter().map(|t| {
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
let objc = self.objc.borrow();
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types];
}
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
}
impl<T> Layout for View<T> {
fn get_backing_node(&self) -> ShareId<Object> {
let objc = self.objc.borrow();
unsafe {
let x: id = msg_send![&**objc, self];
ShareId::from_ptr(x)
}
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
let objc = self.objc.borrow();
unsafe {
let _: () = msg_send![&**objc, addSubview:backing_node];
}
fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler);
}
}