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. //! Implements some functionality 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.
//! //!
//! 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::ffi::CString;
use std::mem; 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 //! Wraps `NSButton` on macOS, and `UIButton` on iOS and tvOS.
//! that this will change at some point. //!
//! 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::fmt;
use std::sync::Once; 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::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel}; use objc::runtime::{Class, Object, Sel};
use objc::{class, msg_send, sel, sel_impl}; 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::invoker::TargetActionHandler;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::text::{AttributedString, Font}; use crate::text::{AttributedString, Font};
use crate::utils::load; use crate::utils::{load, properties::ObjcProperty};
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
use crate::macos::FocusRingType; use crate::macos::FocusRingType;
/// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime mod enums;
/// where our `NSButton` lives. 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)] #[derive(Debug)]
pub struct Button { pub struct Button {
/// A handle for the underlying Objective-C object. /// 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. /// A reference to an image, if set. We keep a copy to avoid any ownership snafus.
pub image: Option<Image>, pub image: Option<Image>,
@ -93,34 +131,34 @@ impl Button {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view), center_y: LayoutAnchorY::center(view),
objc: unsafe { ShareId::from_ptr(view) }, objc: ObjcProperty::retain(view),
} }
} }
/// Sets an image on the underlying button. /// Sets an image on the underlying button.
pub fn set_image(&mut self, image: Image) { pub fn set_image(&mut self, image: Image) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setImage:&*image.0]; let _: () = msg_send![obj, setImage:&*image.0];
} });
self.image = Some(image); 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")] #[cfg(feature = "macos")]
pub fn set_bezel_style(&self, bezel_style: BezelStyle) { pub fn set_bezel_style(&self, bezel_style: BezelStyle) {
let style: NSUInteger = bezel_style.into(); let style: NSUInteger = bezel_style.into();
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setBezelStyle:style]; let _: () = msg_send![obj, setBezelStyle:style];
} });
} }
/// Attaches a callback for button press events. Don't get too creative now... /// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something. /// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action); //let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler); //self.handler = Some(handler);
} }
/// Call this to set the background color for the backing layer. /// Call this to set the background color for the backing layer.
@ -128,10 +166,10 @@ impl Button {
let color: id = color.as_ref().into(); let color: id = color.as_ref().into();
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
unsafe { self.objc.with_mut(|obj| unsafe {
let cell: id = msg_send![&*self.objc, cell]; let cell: id = msg_send![obj, cell];
let _: () = msg_send![cell, setBackgroundColor:color]; 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 /// 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) { pub fn set_key_equivalent(&self, key: &str) {
let key = NSString::new(key); let key = NSString::new(key);
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setKeyEquivalent:&*key]; let _: () = msg_send![obj, setKeyEquivalent:&*key];
} });
} }
/// Sets the text color for this button. /// 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. /// On macOS, this is done by way of an `AttributedString` under the hood.
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) { pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
unsafe { self.objc.with_mut(move |obj| unsafe {
let text: id = msg_send![&*self.objc, attributedTitle]; let text: id = msg_send![obj, attributedTitle];
let len: isize = msg_send![text, length]; let len: isize = msg_send![text, length];
let mut attr_str = AttributedString::wrap(text); 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. // @TODO: Figure out how to handle oddities like this.
/// For buttons on macOS, one might need to disable the border. This does that. /// For buttons on macOS, one might need to disable the border. This does that.
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
pub fn set_bordered(&self, is_bordered: bool) { pub fn set_bordered(&self, is_bordered: bool) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setBordered:match is_bordered { let _: () = msg_send![obj, setBordered:match is_bordered {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// Sets the font for this button. /// Sets the font for this button.
pub fn set_font<F: AsRef<Font>>(&self, font: F) { pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref().clone(); let font = font.as_ref().clone();
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font]; let _: () = msg_send![obj, setFont:&*font];
} });
} }
/// Sets how the control should draw a focus ring when a user is focused on it. /// 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")] #[cfg(feature = "macos")]
pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) { pub fn set_focus_ring_type(&self, focus_ring_type: FocusRingType) {
let ring_type: NSUInteger = focus_ring_type.into(); let ring_type: NSUInteger = focus_ring_type.into();
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setFocusRingType:ring_type]; let _: () = msg_send![obj, setFocusRingType:ring_type];
} });
} }
/// Toggles the highlighted status of the button. /// Toggles the highlighted status of the button.
pub fn set_highlighted(&self, highlight: bool) { pub fn set_highlighted(&self, highlight: bool) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, highlight:match highlight { let _: () = msg_send![obj, highlight:match highlight {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
} }
impl Layout for Button { impl Layout for Button {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.clone() 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, open a discussion on the GitHub repo.
"#);
} }
} }
impl Layout for &Button { impl Layout for &Button {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.clone() 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, open a discussion on the GitHub repo.
"#);
} }
} }
impl Drop for Button { impl Drop for Button {
/// Nils out references on the Objective-C side and removes this from the backing view.
// Just to be sure, let's... nil these out. They should be weak references, // Just to be sure, let's... nil these out. They should be weak references,
// but I'd rather be paranoid and remove them later. // but I'd rather be paranoid and remove them later.
fn drop(&mut self) { fn drop(&mut self) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setTarget:nil]; let _: () = msg_send![obj, setTarget:nil];
let _: () = msg_send![&*self.objc, setAction:nil]; let _: () = msg_send![obj, setAction:nil];
} });
} }
} }
@ -254,96 +280,3 @@ fn register_class() -> *const Class {
unsafe { VIEW_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_NORMAL_CONTRAST: &'static str = "AQUA_DARK_COLOR_NORMAL_CONTRAST";
pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST"; pub(crate) const AQUA_DARK_COLOR_HIGH_CONTRAST: &'static str = "AQUA_DARK_COLOR_HIGH_CONTRAST";
extern "C" { extern "C" {
static NSAppearanceNameAqua: id; static NSAppearanceNameAqua: id;
static NSAppearanceNameAccessibilityHighContrastAqua: id; static NSAppearanceNameAccessibilityHighContrastAqua: id;

View file

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

View file

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

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 /// 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 /// 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). /// 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. /// Adds another Layout-backed control or view as a subview of this view.
fn add_subview<V: Layout>(&self, view: &V) { fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = self.get_backing_node(); self.with_backing_node(|backing_node| {
let subview_node = view.get_backing_node(); view.with_backing_node(|subview_node| unsafe {
let _: () = msg_send![backing_node, addSubview:subview_node];
unsafe { });
let _: () = msg_send![&*backing_node, addSubview:&*subview_node]; });
}
} }
/// Removes a control or view from the superview. /// Removes a control or view from the superview.
fn remove_from_superview(&self) { fn remove_from_superview(&self) {
let backing_node = self.get_backing_node(); self.with_backing_node(|backing_node| unsafe {
let _: () = msg_send![backing_node, removeFromSuperview];
unsafe { });
let _: () = msg_send![&*backing_node, removeFromSuperview];
}
} }
/// Sets the `frame` for the view this trait is applied to. /// 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 /// `set_translates_autoresizing_mask_into_constraints` to enable frame-based layout calls (or
/// use an appropriate initializer for a given view type). /// use an appropriate initializer for a given view type).
fn set_frame<R: Into<CGRect>>(&self, rect: R) { fn set_frame<R: Into<CGRect>>(&self, rect: R) {
let backing_node = self.get_backing_node();
let frame: CGRect = rect.into(); let frame: CGRect = rect.into();
unsafe { self.with_backing_node(move |backing_node| unsafe {
let _: () = msg_send![&*backing_node, setFrame:frame]; let _: () = msg_send![backing_node, setFrame:frame];
} });
} }
/// Sets whether the view for this trait should translate autoresizing masks into layout /// 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, /// 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). /// 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) { fn set_translates_autoresizing_mask_into_constraints(&self, translates: bool) {
let backing_node = self.get_backing_node(); self.with_backing_node(|backing_node| unsafe {
let _: () = msg_send![backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
unsafe {
let _: () = msg_send![&*backing_node, setTranslatesAutoresizingMaskIntoConstraints:match translates {
true => YES, true => YES,
false => NO 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 // @TODO: Finish investing the `Rc` approach, might be able to just take
// ownership and rely on Rust being correct. // ownership and rely on Rust being correct.
let objc = item.objc.borrow(); item.objc.get(|obj| unsafe {
unsafe { msg_send![obj, self]
msg_send![&**objc, self] })
}
} }
extern fn will_display_cell<T: ListViewDelegate>( 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::pasteboard::PasteboardType;
use crate::scrollview::ScrollView; use crate::scrollview::ScrollView;
use crate::utils::{os, CellFactory, CGSize}; use crate::utils::{os, CellFactory, CGSize};
use crate::utils::properties::{ObjcProperty, PropertyNullable};
use crate::view::ViewDelegate; use crate::view::ViewDelegate;
#[cfg(target_os = "macos")] #[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)] #[derive(Debug)]
pub struct ListView<T = ()> { pub struct ListView<T = ()> {
/// Internal map of cell identifers/vendors. These are used for handling dynamic cell /// 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>>, menu: PropertyNullable<Vec<MenuItem>>,
/// A pointer to the Objective-C runtime view controller. /// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>, pub objc: ObjcProperty,
/// On macOS, we need to manage the NSScrollView ourselves. It's a bit /// On macOS, we need to manage the NSScrollView ourselves. It's a bit
/// more old school like that... /// more old school like that...
@ -241,9 +193,9 @@ impl ListView {
let scrollview = { let scrollview = {
let sview = ScrollView::new(); let sview = ScrollView::new();
unsafe { sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*sview.objc, setDocumentView:view]; let _: () = msg_send![obj, setDocumentView:view];
} });
sview sview
}; };
@ -251,7 +203,9 @@ impl ListView {
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView. // For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
// @TODO: Fix this with proper mutable access. // @TODO: Fix this with proper mutable access.
#[cfg(target_os = "macos")] #[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")] #[cfg(target_os = "ios")]
let anchor_view: id = view; let anchor_view: id = view;
@ -270,7 +224,7 @@ impl ListView {
height: LayoutAnchorDimension::height(anchor_view), height: LayoutAnchorDimension::height(anchor_view),
center_x: LayoutAnchorX::center(anchor_view), center_x: LayoutAnchorX::center(anchor_view),
center_y: LayoutAnchorY::center(anchor_view), center_y: LayoutAnchorY::center(anchor_view),
objc: unsafe { ShareId::from_ptr(view) }, objc: ObjcProperty::retain(view),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
scrollview: scrollview scrollview: scrollview
@ -300,16 +254,18 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
let scrollview = { let scrollview = {
let sview = ScrollView::new(); let sview = ScrollView::new();
unsafe { sview.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*sview.objc, setDocumentView:view]; let _: () = msg_send![obj, setDocumentView:view];
} });
sview sview
}; };
// For macOS, we need to use the NSScrollView anchor points, not the NSTableView. // For macOS, we need to use the NSScrollView anchor points, not the NSTableView.
#[cfg(target_os = "macos")] #[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")] #[cfg(target_os = "ios")]
let anchor_view = view; let anchor_view = view;
@ -328,7 +284,7 @@ impl<T> ListView<T> where T: ListViewDelegate + 'static {
height: LayoutAnchorDimension::height(anchor_view), height: LayoutAnchorDimension::height(anchor_view),
center_x: LayoutAnchorX::center(anchor_view), center_x: LayoutAnchorX::center(anchor_view),
center_y: LayoutAnchorY::center(anchor_view), center_y: LayoutAnchorY::center(anchor_view),
objc: unsafe { ShareId::from_ptr(view) }, objc: ObjcProperty::retain(view),
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
scrollview: scrollview scrollview: scrollview
@ -382,7 +338,9 @@ impl<T> ListView<T> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let key = NSString::new(identifier); 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 { if cell != nil {
ListViewRow::from_cached(cell) ListViewRow::from_cached(cell)
@ -398,12 +356,11 @@ impl<T> ListView<T> {
/// Call this to set the background color for the backing layer. /// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
// @TODO: This is wrong. // @TODO: This is wrong.
let color = color.as_ref().cg_color(); self.objc.with_mut(|obj| unsafe {
let color = color.as_ref().cg_color();
unsafe { let layer: id = msg_send![obj, layer];
let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:color]; let _: () = msg_send![layer, setBackgroundColor:color];
} });
} }
/// Sets the style for the underlying NSTableView. This property is only supported on macOS /// 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")] #[cfg(feature = "macos")]
pub fn set_style(&self, style: crate::foundation::NSInteger) { pub fn set_style(&self, style: crate::foundation::NSInteger) {
if os::is_minimum_version(11) { if os::is_minimum_version(11) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setStyle:style]; let _: () = msg_send![obj, setStyle:style];
} });
} }
} }
@ -424,19 +381,19 @@ impl<T> ListView<T> {
/// view for navigation purposes. /// view for navigation purposes.
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
pub fn set_allows_empty_selection(&self, allows: bool) { pub fn set_allows_empty_selection(&self, allows: bool) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setAllowsEmptySelection:match allows { let _: () = msg_send![obj, setAllowsEmptySelection:match allows {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// Set the selection highlight style. /// Set the selection highlight style.
pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) { pub fn set_selection_highlight_style(&self, style: crate::foundation::NSInteger) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setSelectionHighlightStyle:style]; let _: () = msg_send![obj, setSelectionHighlightStyle:style];
} });
} }
/// Select the rows at the specified indexes, optionally adding to any existing selections. /// 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![index_set, addIndex:index];
} }
let _: () = msg_send![&*self.objc, selectRowIndexes:index_set byExtendingSelection:match extends_existing { self.objc.with_mut(|obj| {
true => YES, let _: () = msg_send![obj, selectRowIndexes:index_set byExtendingSelection:match extends_existing {
false => NO true => YES,
}]; false => NO
}];
});
} }
} }
@ -466,14 +425,19 @@ impl<T> ListView<T> {
/// }); /// });
/// ``` /// ```
pub fn perform_batch_updates<F: Fn(ListView)>(&self, update: F) { 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")] #[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(); let handle = self.clone_as_handle();
update(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 // We need to temporarily retain this; it can drop after the underlying NSTableView
// has also retained it. // has also retained it.
let x = ShareId::from_ptr(index_set); 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 ye: id = msg_send![class!(NSIndexSet), indexSetWithIndex:0];
let y = ShareId::from_ptr(ye); 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 // We need to temporarily retain this; it can drop after the underlying NSTableView
// has also retained it. // has also retained it.
let x = ShareId::from_ptr(index_set); 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 /// Sets an enforced row-height; if you need dynamic rows, you'll want to
/// look at ListViewDelegate methods, or use AutoLayout. /// look at ListViewDelegate methods, or use AutoLayout.
pub fn set_row_height(&self, height: CGFloat) { pub fn set_row_height(&self, height: CGFloat) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setRowHeight:height]; let _: () = msg_send![obj, setRowHeight:height];
} });
} }
/// This defaults to true. If you're using manual heights, you may want to set this to `false`, /// 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. /// It can make some scrolling situations much smoother.
pub fn set_uses_automatic_row_heights(&self, uses: bool) { pub fn set_uses_automatic_row_heights(&self, uses: bool) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setUsesAutomaticRowHeights:match uses { let _: () = msg_send![obj, setUsesAutomaticRowHeights:match uses {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// On macOS, this will instruct the underlying NSTableView to alternate /// 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. /// to hard-set a row height as well.
pub fn set_uses_alternating_backgrounds(&self, uses: bool) { pub fn set_uses_alternating_backgrounds(&self, uses: bool) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setUsesAlternatingRowBackgroundColors:match uses { let _: () = msg_send![obj, setUsesAlternatingRowBackgroundColors:match uses {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// End actions for a row. API subject to change. /// End actions for a row. API subject to change.
pub fn set_row_actions_visible(&self, visible: bool) { pub fn set_row_actions_visible(&self, visible: bool) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setRowActionsVisible:match visible { let _: () = msg_send![obj, setRowActionsVisible:match visible {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// Register this view for drag and drop operations. /// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe { let types: NSArray = types.into_iter().map(|t| {
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 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.
// this is just an enum... and this is not an oft called method. let x: NSString = (*t).into();
let x: NSString = (*t).into(); x.into()
x.into() }).collect::<Vec<id>>().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 /// 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 /// Calling this will reload (and redraw) your listview based on whatever the data source
/// reports back. /// reports back.
pub fn reload(&self) { pub fn reload(&self) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, reloadData]; let _: () = msg_send![obj, reloadData];
} });
} }
/// Returns the selected row. /// Returns the selected row.
pub fn get_selected_row_index(&self) -> NSInteger { pub fn get_selected_row_index(&self) -> NSInteger {
unsafe { msg_send![&*self.objc, selectedRow] } self.objc.get(|obj| unsafe { msg_send![obj, selectedRow] })
} }
/// Returns the currently clicked row. This is macOS-specific, and is generally used in context /// 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 { 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> { impl<T> Layout for ListView<T> {
/// On macOS, this returns the NSScrollView, not the NSTableView. fn with_backing_node<F: Fn(id)>(&self, handler: F) {
fn get_backing_node(&self) -> ShareId<Object> { self.objc.with_mut(handler);
#[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];
}
} }
} }
@ -680,13 +632,13 @@ impl<T> Drop for ListView<T> {
/// ///
/// There are, thankfully, no delegates we need to break here. /// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) { fn drop(&mut self) {
if self.delegate.is_some() { /*if self.delegate.is_some() {
unsafe { unsafe {
let superview: id = msg_send![&*self.objc, superview]; let superview: id = msg_send![&*self.objc, superview];
if superview != nil { if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview]; let _: () = msg_send![&*self.objc, removeFromSuperview];
} }
} }
} }*/
} }
} }

View file

@ -53,6 +53,7 @@ use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType; use crate::pasteboard::PasteboardType;
use crate::view::ViewDelegate; use crate::view::ViewDelegate;
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod macos; mod macos;
@ -88,7 +89,7 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
#[derive(Debug)] #[derive(Debug)]
pub struct ListViewRow<T = ()> { pub struct ListViewRow<T = ()> {
/// A pointer to the Objective-C runtime view controller. /// 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. /// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>, pub delegate: Option<Box<T>>,
@ -147,7 +148,7 @@ impl ListViewRow {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::center(view), center_y: LayoutAnchorY::center(view),
objc: Rc::new(RefCell::new(unsafe { Id::from_ptr(view) })), objc: ObjcProperty::retain(view),
}; };
view view
@ -218,7 +219,7 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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()); (&mut delegate).did_load(view.clone_as_handle());
@ -246,17 +247,11 @@ impl<T> ListViewRow<T> where T: ViewDelegate + 'static {
height: self.height.clone(), height: self.height.clone(),
center_x: self.center_x.clone(), center_x: self.center_x.clone(),
center_y: self.center_y.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> { impl<T> ListViewRow<T> {
/// An internal method that returns a clone of this object, sans references to the delegate or /// 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 /// 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(), height: self.height.clone(),
center_x: self.center_x.clone(), center_x: self.center_x.clone(),
center_y: self.center_y.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) { pub fn set_identifier(&self, identifier: &'static str) {
let identifier = NSString::new(identifier); let identifier = NSString::new(identifier);
let objc = self.objc.borrow(); self.objc.with_mut(|obj| unsafe {
unsafe { let _: () = msg_send![obj, setIdentifier:&*identifier];
let _: () = msg_send![&**objc, setIdentifier:&*identifier]; });
}
} }
/// Call this to set the background color for the backing layer. /// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) { 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(); let color: id = color.as_ref().into();
unsafe { self.objc.with_mut(|obj| unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color); (&mut *obj).set_ivar(BACKGROUND_COLOR, color);
} });
} }
/// Register this view for drag and drop operations. /// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe { let types: NSArray = types.into_iter().map(|t| {
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 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.
// this is just an enum... and this is not an oft called method. let x: NSString = (*t).into();
let x: NSString = (*t).into(); x.into()
x.into() }).collect::<Vec<id>>().into();
}).collect::<Vec<id>>().into();
let objc = self.objc.borrow(); self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types]; let _: () = msg_send![obj, registerForDraggedTypes:&*types];
} });
} }
} }
impl<T> Layout for ListViewRow<T> { impl<T> Layout for ListViewRow<T> {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
let objc = self.objc.borrow(); self.objc.with_mut(handler);
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];
}
} }
} }

View file

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

View file

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

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::foundation::{id, nil, BOOL, YES, NO, NSString};
use crate::invoker::TargetActionHandler; use crate::invoker::TargetActionHandler;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; 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 /// A wrapper for `NSSwitch`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSSwitch` lives. /// where our `NSSwitch` lives.
#[derive(Debug)] #[derive(Debug)]
pub struct Switch { pub struct Switch {
/// A pointer to the underlying Objective-C Object. /// A pointer to the underlying Objective-C Object.
pub objc: ShareId<Object>, pub objc: ObjcProperty,
handler: Option<TargetActionHandler>, handler: Option<TargetActionHandler>,
/// A pointer to the Objective-C runtime top layout constraint. /// A pointer to the Objective-C runtime top layout constraint.
@ -79,35 +79,35 @@ impl Switch {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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. /// Sets whether this is checked on or off.
pub fn set_checked(&mut self, checked: bool) { 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, // @TODO: The constants to use here changed back in 10.13ish, so... do we support that,
// or just hide it? // or just hide it?
let _: () = msg_send![&*self.objc, setState:match checked { let _: () = msg_send![obj, setState:match checked {
true => 1, true => 1,
false => 0 false => 0
}]; }];
} });
} }
/// Attaches a callback for button press events. Don't get too creative now... /// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something. /// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
let handler = TargetActionHandler::new(&*self.objc, action); //let handler = TargetActionHandler::new(&*self.objc, action);
self.handler = Some(handler); //self.handler = Some(handler);
} }
} }
impl Layout for Switch { impl Layout for Switch {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.clone() self.objc.with_mut(handler);
} }
fn add_subview<V: Layout>(&self, _view: &V) { fn add_subview<V: Layout>(&self, _view: &V) {
panic!(r#" panic!(r#"
Tried to add a subview to a Button. This is not allowed in Cacao. If you think this should be supported, 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, // Just to be sure, let's... nil these out. They should be weak references,
// but I'd rather be paranoid and remove them later. // but I'd rather be paranoid and remove them later.
fn drop(&mut self) { fn drop(&mut self) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setTarget:nil]; let _: () = msg_send![obj, setTarget:nil];
let _: () = msg_send![&*self.objc, setAction: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::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::text::{Font, TextAlign, LineBreakMode}; use crate::text::{Font, TextAlign, LineBreakMode};
use crate::utils::properties::ObjcProperty;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod 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 /// A clone-able handler to an `NSTextField/UILabel` reference in the
/// Objective-C runtime. /// 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)] #[derive(Debug)]
pub struct Label<T = ()> { pub struct Label<T = ()> {
/// A pointer to the Objective-C runtime view controller. /// A pointer to the Objective-C runtime view controller.
pub objc: ShareId<Object>, pub objc: ObjcProperty,
/// A pointer to the delegate for this view. /// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>, pub delegate: Option<Box<T>>,
@ -152,7 +194,7 @@ impl Label {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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), height: LayoutAnchorDimension::height(label),
center_x: LayoutAnchorX::center(label), center_x: LayoutAnchorX::center(label),
center_y: LayoutAnchorY::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()); //(&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. /// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
// @TODO: This is wrong. // @TODO: This is wrong.
let color = color.as_ref().cg_color(); // Needs to set ivar and such, akin to View.
self.objc.with_mut(|obj| unsafe {
unsafe { let color = color.as_ref().cg_color();
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![obj, layer];
let _: () = msg_send![layer, setBackgroundColor:color]; let _: () = msg_send![layer, setBackgroundColor:color];
} });
} }
/// Call this to set the color of the text. /// Call this to set the color of the text.
pub fn set_text_color<C: AsRef<Color>>(&self, color: C) { pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
let color: id = color.as_ref().into(); let color: id = color.as_ref().into();
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setTextColor:color]; let _: () = msg_send![obj, setTextColor:color];
} });
} }
/// Call this to set the text for the label. /// Call this to set the text for the label.
@ -239,26 +281,24 @@ impl<T> Label<T> {
let text = text.as_ref(); let text = text.as_ref();
let s = NSString::new(text); let s = NSString::new(text);
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setStringValue:&*s]; let _: () = msg_send![obj, setStringValue:&*s];
} });
} }
/// Retrieve the text currently held in the label. /// Retrieve the text currently held in the label.
pub fn get_text(&self) -> String { pub fn get_text(&self) -> String {
let s = NSString::retain(unsafe { self.objc.get(|obj| unsafe {
msg_send![&*self.objc, stringValue] NSString::retain(msg_send![obj, stringValue]).to_string()
}); })
s.to_string()
} }
/// Sets the text alignment for this label. /// Sets the text alignment for this label.
pub fn set_text_alignment(&self, alignment: TextAlign) { pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe { self.objc.with_mut(|obj| unsafe {
let alignment: NSInteger = alignment.into(); let alignment: NSInteger = alignment.into();
let _: () = msg_send![&*self.objc, setAlignment:alignment]; let _: () = msg_send![obj, setAlignment:alignment];
} });
} }
/// Sets the font for this label. /// 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. // font object - it seems like it can be optimized away otherwise.
let font = font.as_ref().clone(); let font = font.as_ref().clone();
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font]; let _: () = msg_send![obj, setFont:&*font];
} });
} }
/// Set whether this is hidden or not. /// Set whether this is hidden or not.
pub fn set_hidden(&self, hidden: bool) { pub fn set_hidden(&self, hidden: bool) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden { let _: () = msg_send![obj, setHidden:match hidden {
true => YES, true => YES,
false => NO false => NO
}]; }];
} });
} }
/// Sets the maximum number of lines. /// Sets the maximum number of lines.
pub fn set_max_number_of_lines(&self, num: NSInteger) { pub fn set_max_number_of_lines(&self, num: NSInteger) {
unsafe { self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&*self.objc, setMaximumNumberOfLines:num]; let _: () = msg_send![obj, setMaximumNumberOfLines:num];
} });
} }
/// Set the line break mode for this label. /// Set the line break mode for this label.
pub fn set_line_break_mode(&self, mode: LineBreakMode) { pub fn set_line_break_mode(&self, mode: LineBreakMode) {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { self.objc.with_mut(|obj| unsafe {
let cell: id = msg_send![&*self.objc, cell]; let cell: id = msg_send![obj, cell];
let mode = mode as NSUInteger; let mode = mode as NSUInteger;
let _: () = msg_send![cell, setTruncatesLastVisibleLine:YES]; let _: () = msg_send![cell, setTruncatesLastVisibleLine:YES];
let _: () = msg_send![cell, setLineBreakMode:mode]; let _: () = msg_send![cell, setLineBreakMode:mode];
} });
} }
} }
impl<T> Layout for Label<T> { impl<T> Layout for Label<T> {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.clone() self.objc.with_mut(handler);
}
fn add_subview<V: Layout>(&self, view: &V) {
let backing_node = view.get_backing_node();
unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node];
}
} }
} }
@ -323,13 +355,13 @@ impl<T> Drop for Label<T> {
/// ///
/// There are, thankfully, no delegates we need to break here. /// There are, thankfully, no delegates we need to break here.
fn drop(&mut self) { fn drop(&mut self) {
if self.delegate.is_some() { /*if self.delegate.is_some() {
unsafe { unsafe {
let superview: id = msg_send![&*self.objc, superview]; let superview: id = msg_send![&*self.objc, superview];
if superview != nil { if superview != nil {
let _: () = msg_send![&*self.objc, removeFromSuperview]; let _: () = msg_send![&*self.objc, removeFromSuperview];
} }
} }
} }*/
} }
} }

View file

@ -16,6 +16,7 @@ mod cell_factory;
pub use cell_factory::CellFactory; pub use cell_factory::CellFactory;
pub mod os; pub mod os;
pub mod properties;
/// A generic trait that's used throughout multiple different controls in this framework - acts as /// A generic trait that's used throughout multiple different controls in this framework - acts as
/// a guard for whether something is a (View|Window|etc)Controller. /// 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); (&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) 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::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType; use crate::pasteboard::PasteboardType;
use crate::utils::properties::ObjcProperty;
use std::rc::Rc;
use std::cell::RefCell;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod macos; mod macos;
@ -96,7 +94,7 @@ fn common_init(class: *const Class) -> id {
#[derive(Debug)] #[derive(Debug)]
pub struct View<T = ()> { pub struct View<T = ()> {
/// A pointer to the Objective-C runtime view controller. /// 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. /// A pointer to the delegate for this view.
pub delegate: Option<Box<T>>, pub delegate: Option<Box<T>>,
@ -155,7 +153,7 @@ impl View {
height: LayoutAnchorDimension::height(view), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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), height: LayoutAnchorDimension::height(view),
center_x: LayoutAnchorX::center(view), center_x: LayoutAnchorX::center(view),
center_y: LayoutAnchorY::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()); (&mut delegate).did_load(view.clone_as_handle());
@ -214,51 +212,35 @@ impl<T> View<T> {
height: self.height.clone(), height: self.height.clone(),
center_x: self.center_x.clone(), center_x: self.center_x.clone(),
center_y: self.center_y.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. /// Call this to set the background color for the backing layer.
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) { 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(); let color: id = color.as_ref().into();
unsafe { self.objc.with_mut(|obj| unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color); (&mut *obj).set_ivar(BACKGROUND_COLOR, color);
} });
} }
/// Register this view for drag and drop operations. /// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) { pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
unsafe { let types: NSArray = types.into_iter().map(|t| {
let types: NSArray = types.into_iter().map(|t| { let x: NSString = (*t).into();
let x: NSString = (*t).into(); x.into()
x.into() }).collect::<Vec<id>>().into();
}).collect::<Vec<id>>().into();
let objc = self.objc.borrow(); self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![&**objc, registerForDraggedTypes:&*types]; let _: () = msg_send![obj, registerForDraggedTypes:&*types];
} });
} }
} }
impl<T> Layout for View<T> { impl<T> Layout for View<T> {
fn get_backing_node(&self) -> ShareId<Object> { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
let objc = self.objc.borrow(); self.objc.with_mut(handler);
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];
}
} }
} }