diff --git a/src/bundle.rs b/src/bundle.rs index 4760a10..aa6ff2c 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -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; diff --git a/src/button/enums.rs b/src/button/enums.rs new file mode 100644 index 0000000..6b30521 --- /dev/null +++ b/src/button/enums.rs @@ -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 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 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) + } + } +} diff --git a/src/button/mod.rs b/src/button/mod.rs index 5975946..2d70855 100644 --- a/src/button/mod.rs +++ b/src/button/mod.rs @@ -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, + pub objc: ObjcProperty, /// A reference to an image, if set. We keep a copy to avoid any ownership snafus. pub image: Option, @@ -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(&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>(&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>(&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 { - self.objc.clone() - } - - fn add_subview(&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(&self, handler: F) { + self.objc.with_mut(handler); } } impl Layout for &Button { - fn get_backing_node(&self) -> ShareId { - self.objc.clone() - } - - fn add_subview(&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(&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 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 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) - } - } -} diff --git a/src/color/macos_dynamic_color.rs b/src/color/macos_dynamic_color.rs index 10d7db6..cee8521 100644 --- a/src/color/macos_dynamic_color.rs +++ b/src/color/macos_dynamic_color.rs @@ -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; diff --git a/src/geometry.rs b/src/geometry.rs index 68f025f..cd8d529 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -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. diff --git a/src/image/mod.rs b/src/image/mod.rs index d8cf22e..1597733 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -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, + 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>(&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 { - self.objc.clone() - } - - fn add_subview(&self, view: &V) { - let backing_node = view.get_backing_node(); - - unsafe { - let _: () = msg_send![&*self.objc, addSubview:backing_node]; - } + fn with_backing_node(&self, handler: F) { + self.objc.with_mut(handler); } } diff --git a/src/input/mod.rs b/src/input/mod.rs index dcfbd72..a5db174 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -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 { /// A pointer to the Objective-C runtime view controller. - pub objc: ShareId, + pub objc: ObjcProperty, /// A pointer to the delegate for this view. pub delegate: Option>, @@ -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 TextField { /// 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>(&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>(&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 Layout for TextField { - fn get_backing_node(&self) -> ShareId { - self.objc.clone() - } - - fn add_subview(&self, view: &V) { - let backing_node = view.get_backing_node(); - - unsafe { - let _: () = msg_send![&*self.objc, addSubview: backing_node]; - } + fn with_backing_node(&self, handler: F) { + self.objc.with_mut(handler); } } @@ -277,13 +267,13 @@ impl Drop for TextField { /// /// 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]; } } - } + }*/ } } diff --git a/src/layout/traits.rs b/src/layout/traits.rs index 83714a6..260eebe 100644 --- a/src/layout/traits.rs +++ b/src/layout/traits.rs @@ -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; + fn with_backing_node(&self, handler: F); /// Adds another Layout-backed control or view as a subview of this view. fn add_subview(&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>(&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 }]; - } + }); } } diff --git a/src/listview/macos.rs b/src/listview/macos.rs index fa3749e..a6c6010 100644 --- a/src/listview/macos.rs +++ b/src/listview/macos.rs @@ -52,10 +52,9 @@ extern fn view_for_column( // // @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( diff --git a/src/listview/mod.rs b/src/listview/mod.rs index 29f5683..cf1e899 100644 --- a/src/listview/mod.rs +++ b/src/listview/mod.rs @@ -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>>); - -impl ObjcProperty { - pub fn new(obj: id) -> Self { - Self(Rc::new(RefCell::new(unsafe { - Id::from_ptr(obj) - }))) - } - - pub fn with(&self, handler: F) - where - F: Fn(&Object) - { - let borrow = self.0.borrow(); - handler(&borrow); - } -} - -#[derive(Debug, Default)] -pub struct PropertyNullable(Rc>>); - -impl PropertyNullable { - 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(&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 { /// Internal map of cell identifers/vendors. These are used for handling dynamic cell @@ -184,7 +136,7 @@ pub struct ListView { menu: PropertyNullable>, /// A pointer to the Objective-C runtime view controller. - pub objc: ShareId, + 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 ListView 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 ListView 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 ListView { #[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 ListView { /// Call this to set the background color for the backing layer. pub fn set_background_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 ListView { #[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 ListView { /// 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 ListView { 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 ListView { /// }); /// ``` pub fn perform_batch_updates(&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 ListView { // 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 ListView { 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 ListView { // 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 ListView { /// 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 ListView { /// 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::>().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::>().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 ListView { /// 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 ListView { /// } /// ``` pub fn get_clicked_row_index(&self) -> NSInteger { - unsafe { msg_send![&*self.objc, clickedRow] } + self.objc.get(|obj| unsafe { msg_send![obj, clickedRow] }) } } impl Layout for ListView { - /// On macOS, this returns the NSScrollView, not the NSTableView. - fn get_backing_node(&self) -> ShareId { - #[cfg(target_os = "macos")] - let val = self.scrollview.objc.clone(); - - #[cfg(target_os = "ios")] - let val = self.objc.clone(); - - val - } - - fn add_subview(&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(&self, handler: F) { + self.objc.with_mut(handler); } } @@ -680,13 +632,13 @@ impl Drop for ListView { /// /// 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]; } } - } + }*/ } } diff --git a/src/listview/row/mod.rs b/src/listview/row/mod.rs index f916a70..10f491c 100644 --- a/src/listview/row/mod.rs +++ b/src/listview/row/mod.rs @@ -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 { /// A pointer to the Objective-C runtime view controller. - pub objc: Rc>>, + pub objc: ObjcProperty, /// A pointer to the delegate for this view. pub delegate: Option>, @@ -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 ListViewRow 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 ListViewRow 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 ListViewRow 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 From<&ListViewRow> for ShareId { - fn from(row: &ListViewRow) -> ShareId { - row.objc.clone() - } -}*/ - impl ListViewRow { /// 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 ListViewRow { 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 ListViewRow { 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>(&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::>().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::>().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 Layout for ListViewRow { - fn get_backing_node(&self) -> ShareId { - 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(&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(&self, handler: F) { + self.objc.with_mut(handler); } } diff --git a/src/macos/toolbar/item.rs b/src/macos/toolbar/item.rs index 57df07b..3c952e8 100644 --- a/src/macos/toolbar/item.rs +++ b/src/macos/toolbar/item.rs @@ -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); } diff --git a/src/macos/window/mod.rs b/src/macos/window/mod.rs index 2eef4f6..85cd03a 100644 --- a/src/macos/window/mod.rs +++ b/src/macos/window/mod.rs @@ -289,11 +289,9 @@ impl Window { /// Given a view, sets it as the content view for this window. pub fn set_content_view(&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. diff --git a/src/pasteboard/types.rs b/src/pasteboard/types.rs index 8b03b67..90e853a 100644 --- a/src/pasteboard/types.rs +++ b/src/pasteboard/types.rs @@ -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. diff --git a/src/progress/mod.rs b/src/progress/mod.rs index 779a049..57a8638 100644 --- a/src/progress/mod.rs +++ b/src/progress/mod.rs @@ -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, + 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 { - self.objc.clone() - } - - fn add_subview(&self, view: &V) { - let backing_node = view.get_backing_node(); - - unsafe { - let _: () = msg_send![&*self.objc, addSubview:backing_node]; - } + fn with_backing_node(&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]; } - } + }*/ } } diff --git a/src/scrollview/mod.rs b/src/scrollview/mod.rs index 3e51d9e..0820159 100644 --- a/src/scrollview/mod.rs +++ b/src/scrollview/mod.rs @@ -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 { /// A pointer to the Objective-C runtime view controller. - pub objc: ShareId, + pub objc: ObjcProperty, /// A pointer to the delegate for this view. pub delegate: Option>, @@ -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 ScrollView 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 ScrollView { /// Call this to set the background color for the backing layer. pub fn set_background_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 Layout for ScrollView { - fn get_backing_node(&self) -> ShareId { - self.objc.clone() - } - - fn add_subview(&self, view: &V) { - let backing_node = view.get_backing_node(); - - unsafe { - let _: () = msg_send![&*self.objc, addSubview:backing_node]; - } + fn with_backing_node(&self, handler: F) { + self.objc.with_mut(handler); } } @@ -243,13 +235,13 @@ impl Drop for ScrollView { /// /// 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]; } } - } + }*/ } } diff --git a/src/switch.rs b/src/switch.rs index cb60b09..f3b5242 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -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, + pub objc: ObjcProperty, handler: Option, /// 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(&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 { - self.objc.clone() + fn with_backing_node(&self, handler: F) { + self.objc.with_mut(handler); } - + fn add_subview(&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]; + }); } } diff --git a/src/text/label/mod.rs b/src/text/label/mod.rs index 2ecffde..7c4d46f 100644 --- a/src/text/label/mod.rs +++ b/src/text/label/mod.rs @@ -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 { /// A pointer to the Objective-C runtime view controller. - pub objc: ShareId, + pub objc: ObjcProperty, /// A pointer to the delegate for this view. pub delegate: Option>, @@ -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 Label 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 Label { /// Call this to set the background color for the backing layer. pub fn set_background_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>(&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 Label { 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 Label { // 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 Layout for Label { - fn get_backing_node(&self) -> ShareId { - self.objc.clone() - } - - fn add_subview(&self, view: &V) { - let backing_node = view.get_backing_node(); - - unsafe { - let _: () = msg_send![&*self.objc, addSubview:backing_node]; - } + fn with_backing_node(&self, handler: F) { + self.objc.with_mut(handler); } } @@ -323,13 +355,13 @@ impl Drop for Label { /// /// 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]; } } - } + }*/ } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c9cdcef..c5fc344 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -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. diff --git a/src/utils/properties.rs b/src/utils/properties.rs new file mode 100644 index 0000000..4cbe37f --- /dev/null +++ b/src/utils/properties.rs @@ -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>>); + +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(&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>(&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(Rc>>); + +impl PropertyNullable { + 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(&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); + } +} diff --git a/src/view/controller/mod.rs b/src/view/controller/mod.rs index ccd55b0..7278a9f 100644 --- a/src/view/controller/mod.rs +++ b/src/view/controller/mod.rs @@ -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) }; diff --git a/src/view/mod.rs b/src/view/mod.rs index dfa5f76..55976b3 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -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 { /// A pointer to the Objective-C runtime view controller. - pub objc: Rc>>, + pub objc: ObjcProperty, /// A pointer to the delegate for this view. pub delegate: Option>, @@ -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 View 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 View { 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>(&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::>().into(); + let types: NSArray = types.into_iter().map(|t| { + let x: NSString = (*t).into(); + x.into() + }).collect::>().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 Layout for View { - fn get_backing_node(&self) -> ShareId { - let objc = self.objc.borrow(); - - unsafe { - let x: id = msg_send![&**objc, self]; - ShareId::from_ptr(x) - } - } - - fn add_subview(&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(&self, handler: F) { + self.objc.with_mut(handler); } }