Ongoing push to a v0.1.

- Basic support for `AttributedString` type.
- `Debug` implementations across the board to ease debugging issues.
- Methods that take `Color` and `Font` types now accept an `AsRef` to
  make implementing less of a headache.
- Cleanup of the `utils` module.
This commit is contained in:
Ryan McGrath 2021-03-05 14:11:17 -08:00
parent 10c513edad
commit bc54b49475
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
34 changed files with 506 additions and 262 deletions

View file

@ -14,16 +14,12 @@ use crate::image::Image;
use crate::foundation::{id, nil, BOOL, YES, NO, NSString, NSUInteger}; 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::Font; use crate::text::{AttributedString, Font};
use crate::utils::load; use crate::utils::load;
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
use crate::macos::FocusRingType; use crate::macos::FocusRingType;
extern "C" {
static NSForegroundColorAttributeName: id;
}
/// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime /// A wrapper for `NSButton`. Holds (retains) pointers for the Objective-C runtime
/// where our `NSButton` lives. /// where our `NSButton` lives.
#[derive(Debug)] #[derive(Debug)]
@ -111,17 +107,13 @@ impl Button {
} }
/// 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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.into_platform_specific_color(); let color: id = color.as_ref().into();
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
unsafe { unsafe {
let cell: id = msg_send![&*self.objc, cell]; let cell: id = msg_send![&*self.objc, cell];
let _: () = msg_send![cell, setBackgroundColor:bg]; let _: () = msg_send![cell, setBackgroundColor:color];
/*let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg];
*/
} }
} }
@ -133,20 +125,19 @@ impl Button {
} }
} }
pub fn set_text_color(&self, color: Color) { /// Sets the text color for this button.
let bg = color.into_platform_specific_color(); ///
/// On macOS, this is done by way of an `AttributedString` under the hood.
// @TODO: Clean this up, and look at just using `CFMutableAttributedString` instead pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
// to avoid ObjC overhead. #[cfg(feature = "macos")]
unsafe { unsafe {
let alloc: id = msg_send![class!(NSMutableAttributedString), alloc]; let text: id = msg_send![&*self.objc, attributedTitle];
let s: id = msg_send![&*self.objc, attributedTitle]; let len: isize = msg_send![text, length];
let attributed_string: id = msg_send![alloc, initWithAttributedString:s];
let len: isize = msg_send![s, length];
let range = core_foundation::base::CFRange::init(0, len);
let _: () = msg_send![attributed_string, addAttribute:NSForegroundColorAttributeName value:bg range:range]; let mut attr_str = AttributedString::wrap(text);
let _: () = msg_send![&*self.objc, setAttributedTitle:attributed_string]; attr_str.set_text_color(color, 0..len);
let _: () = msg_send![&*self.objc, setAttributedTitle:&*attr_str];
} }
} }
@ -163,9 +154,11 @@ impl Button {
} }
/// Sets the font for this button. /// Sets the font for this button.
pub fn set_font(&self, font: &Font) { pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref().clone();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font.objc]; let _: () = msg_send![&*self.objc, setFont:&*font];
} }
} }
@ -197,8 +190,8 @@ impl Layout for Button {
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.
open a discussion on the GitHub repo. If you think this should be supported, open a discussion on the GitHub repo.
"#); "#);
} }
} }
@ -210,8 +203,8 @@ impl Layout for &Button {
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.
open a discussion on the GitHub repo. If you think this should be supported, open a discussion on the GitHub repo.
"#); "#);
} }
} }

View file

@ -14,12 +14,14 @@
/// ///
/// @TODO: bundle iOS/tvOS support. /// @TODO: bundle iOS/tvOS support.
use std::sync::{Arc, RwLock};
use core_graphics::base::CGFloat; use core_graphics::base::CGFloat;
use core_graphics::color::CGColor; use core_graphics::color::CGColor;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::Id;
use crate::foundation::id; use crate::foundation::id;
use crate::utils::os; use crate::utils::os;
@ -37,7 +39,7 @@ use macos_dynamic_color::{
/// In the event that a new variant is introduced in later versions of /// In the event that a new variant is introduced in later versions of
/// macOS or iOS, calls that use the dynamic color(s) from here will likely /// macOS or iOS, calls that use the dynamic color(s) from here will likely
/// default to the `Light` theme. /// default to the `Light` theme.
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub enum Theme { pub enum Theme {
/// The "default" theme on a platform. On macOS, this is Aqua. /// The "default" theme on a platform. On macOS, this is Aqua.
/// On iOS and tvOS, this is whatever you call the system defined theme. /// On iOS and tvOS, this is whatever you call the system defined theme.
@ -48,7 +50,7 @@ pub enum Theme {
} }
/// Represents the contrast level for a rendering context. /// Represents the contrast level for a rendering context.
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub enum Contrast { pub enum Contrast {
/// The default contrast level for the system. /// The default contrast level for the system.
Normal, Normal,
@ -60,7 +62,7 @@ pub enum Contrast {
/// A `Style` is passed to you when doing dynamic color calculations. You can opt to /// A `Style` is passed to you when doing dynamic color calculations. You can opt to
/// provide different colors depending on the settings in here - notably, this is useful /// provide different colors depending on the settings in here - notably, this is useful
/// for supporting dark mode and high contrast accessibility contexts. /// for supporting dark mode and high contrast accessibility contexts.
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub struct Style { pub struct Style {
/// Represents the current theme for where this color may render. /// Represents the current theme for where this color may render.
pub theme: Theme, pub theme: Theme,
@ -69,39 +71,22 @@ pub struct Style {
pub contrast: Contrast pub contrast: Contrast
} }
/*
#[derive(Clone)]
pub struct Property(Rc<RefCell<Id<Object>>>);
impl Property {
pub fn new(obj: id) -> Self {
Property(Rc::new(RefCell::new(Id::from_ptr(obj))))
}
}
#[derive(Clone)]
pub struct ThreadSafeProperty(Arc<RwLock<Id<Object>>>);
impl Property {
pub fn new(obj: id) -> Self {
Property(Rc::new(RefCell::new(Id::from_ptr(obj))))
}
}
*/
/// Represents a Color. You can create custom colors using the various /// Represents a Color. You can create custom colors using the various
/// initializers, or opt to use a system-provided color. The system provided /// initializers, or opt to use a system-provided color. The system provided
/// colors will automatically switch to the "correct" colors/shades depending on whether /// colors will automatically switch to the "correct" colors/shades depending on whether
/// the user is in light or dark mode; to support this with custom colors, be sure /// the user is in light or dark mode; to support this with custom colors, you can create a
/// to call the `.dark()` method after initializing. /// `dynamic` color with a custom handler that determines a color depending on a variety of system
#[derive(Clone)] /// settings.
///
/// This enum is thread-safe, so clone away as needed.
#[derive(Clone, Debug)]
pub enum Color { pub enum Color {
/// Represents an `NSColor` on macOS, and a `UIColor` everywhere else. You typically /// Represents an `NSColor` on macOS, and a `UIColor` everywhere else. You typically
/// don't create this variant yourself; use the initializers found on this enum. /// don't create this variant yourself; use the initializers found on this enum.
/// ///
/// If you need to do custom work not covered by this enum, you can drop to /// If you need to do custom work not covered by this enum, you can drop to
/// the Objective-C level yourself and wrap your color in this. /// the Objective-C level yourself and wrap your color in this.
Object(ShareId<Object>), Custom(Arc<RwLock<Id<Object>>>),
/// The system-provided black. Harsh - you probably don't want to use this. /// The system-provided black. Harsh - you probably don't want to use this.
SystemBlack, SystemBlack,
@ -244,9 +229,13 @@ pub enum Color {
/// The un-adaptable color for text on a dark background. /// The un-adaptable color for text on a dark background.
LightText, LightText,
WindowBackgroundColor, /// The background color for a given window in the system theme.
#[cfg(feature = "macos")]
MacOSWindowBackgroundColor,
UnderPageBackgroundColor /// The background color that should appear under a page per the system theme.
#[cfg(feature = "macos")]
MacOSUnderPageBackgroundColor
} }
impl Color { impl Color {
@ -264,10 +253,10 @@ impl Color {
#[cfg(feature = "ios")] #[cfg(feature = "ios")]
let color = class!(UIColor); let color = class!(UIColor);
Color::Object(unsafe { Color::Custom(Arc::new(RwLock::new(unsafe {
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
ShareId::from_ptr(msg_send![color, colorWithCalibratedRed:r green:g blue:b alpha:a]) Id::from_ptr(msg_send![color, colorWithCalibratedRed:r green:g blue:b alpha:a])
}) })))
} }
/// Creates and returns a color in the RGB space, with the alpha level /// Creates and returns a color in the RGB space, with the alpha level
@ -290,10 +279,10 @@ impl Color {
#[cfg(feature = "ios")] #[cfg(feature = "ios")]
let color = class!(UIColor); let color = class!(UIColor);
Color::Object(unsafe { Color::Custom(Arc::new(RwLock::new(unsafe {
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
ShareId::from_ptr(msg_send![color, colorWithCalibratedHue:h saturation:s brightness:b alpha:a]) Id::from_ptr(msg_send![color, colorWithCalibratedHue:h saturation:s brightness:b alpha:a])
}) })))
} }
/// Creates and returns a color in the RGB space, with the alpha level /// Creates and returns a color in the RGB space, with the alpha level
@ -311,10 +300,10 @@ impl Color {
#[cfg(feature = "ios")] #[cfg(feature = "ios")]
let color = class!(UIColor); let color = class!(UIColor);
Color::Object(unsafe { Color::Custom(Arc::new(RwLock::new(unsafe {
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
ShareId::from_ptr(msg_send![color, colorWithCalibratedWhite:level alpha:alpha]) Id::from_ptr(msg_send![color, colorWithCalibratedWhite:level alpha:alpha])
}) })))
} }
/// Creates and returns a white Color with the specified level or intensity, with the alpha /// Creates and returns a white Color with the specified level or intensity, with the alpha
@ -327,7 +316,7 @@ impl Color {
/// ///
/// This method is not an ideal one to use, but is offered as a convenience method for those /// This method is not an ideal one to use, but is offered as a convenience method for those
/// coming from other environments where these are more common. /// coming from other environments where these are more common.
pub fn hexa(hex: &str, alpha: u8) -> Self { pub fn hexa(_hex: &str, _alpha: u8) -> Self {
Color::SystemRed Color::SystemRed
} }
@ -351,45 +340,53 @@ impl Color {
where where
F: Fn(Style) -> Color + 'static F: Fn(Style) -> Color + 'static
{ {
// It's *possible* that we shouldn't cache these up-front and let them be truly dynamically
// allocated, but this is fine for now (and more predictable, even if perhaps wrong). I'm
// not entirely clear on how expensive the dynamic allocation would be pre-10.15/11.0 and
// am happy to do this for now and let someone who needs true dynamic allocation look into
// it and PR it.
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
Color::Object(unsafe { Color::Custom(Arc::new(RwLock::new(unsafe {
let color: id = msg_send![macos_dynamic_color::register_class(), new]; let color: id = msg_send![macos_dynamic_color::register_class(), new];
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST, handler(Style { (&mut *color).set_ivar(AQUA_LIGHT_COLOR_NORMAL_CONTRAST, {
theme: Theme::Light, let color: id = handler(Style {
contrast: Contrast::Normal theme: Theme::Light,
}).to_objc()); contrast: Contrast::Normal
}).into();
(&mut *color).set_ivar(AQUA_LIGHT_COLOR_HIGH_CONTRAST, handler(Style { color
theme: Theme::Light, });
contrast: Contrast::High
}).to_objc());
(&mut *color).set_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST, handler(Style { (&mut *color).set_ivar(AQUA_LIGHT_COLOR_HIGH_CONTRAST, {
theme: Theme::Dark, let color: id = handler(Style {
contrast: Contrast::Normal theme: Theme::Light,
}).to_objc()); contrast: Contrast::High
}).into();
(&mut *color).set_ivar(AQUA_DARK_COLOR_HIGH_CONTRAST, handler(Style { color
theme: Theme::Light, });
contrast: Contrast::Normal
}).to_objc());
ShareId::from_ptr(color) (&mut *color).set_ivar(AQUA_DARK_COLOR_NORMAL_CONTRAST, {
}) let color: id = handler(Style {
} theme: Theme::Dark,
contrast: Contrast::Normal
}).into();
/// Returns a pointer that can be used for the Objective-C runtime. color
/// });
/// This method is primarily for internal use, but is kept public for those who might need to
/// work with colors outside of what's available in this enum.
pub fn to_objc(&self) -> id {
unsafe { to_objc(self) }
}
/// Legacy. (&mut *color).set_ivar(AQUA_DARK_COLOR_HIGH_CONTRAST, {
pub fn into_platform_specific_color(&self) -> id { let color: id = handler(Style {
unsafe { to_objc(self) } theme: Theme::Light,
contrast: Contrast::Normal
}).into();
color
});
Id::from_ptr(color)
})))
} }
/// Returns a CGColor, which can be used in Core Graphics calls as well as other areas. /// Returns a CGColor, which can be used in Core Graphics calls as well as other areas.
@ -401,7 +398,7 @@ impl Color {
pub fn cg_color(&self) -> CGColor { pub fn cg_color(&self) -> CGColor {
// @TODO: This should probably return a CGColorRef... // @TODO: This should probably return a CGColorRef...
unsafe { unsafe {
let objc = to_objc(self); let objc: id = self.into();
msg_send![objc, CGColor] msg_send![objc, CGColor]
} }
} }
@ -415,19 +412,33 @@ impl AsRef<Color> for Color {
} }
} }
impl From<Color> for id {
/// Consumes and returns the pointer to the underlying Color.
fn from(color: Color) -> Self {
unsafe { to_objc(&color) }
}
}
impl From<&Color> for id {
/// Consumes and returns the pointer to the underlying Color.
fn from(color: &Color) -> Self {
unsafe { to_objc(color) }
}
}
/// Handles color fallback for system-provided colors. /// Handles color fallback for system-provided colors.
macro_rules! system_color_with_fallback { macro_rules! system_color_with_fallback {
($class:ident, $color:ident, $fallback:ident) => ({ ($class:ident, $color:ident, $fallback:ident) => ({
#[cfg(feature = "macos")] #[cfg(feature = "macos")]
{ {
#[cfg(feature = "color_fallbacks")] #[cfg(feature = "color-fallbacks")]
if os::minimum_semversion(10, 10, 0) { if os::minimum_semversion(10, 10, 0) {
msg_send![$class, $color] msg_send![$class, $color]
} else { } else {
msg_send![$class, $fallback] msg_send![$class, $fallback]
} }
#[cfg(not(feature = "color_fallbacks"))] #[cfg(not(feature = "color-fallbacks"))]
msg_send![$class, $color] msg_send![$class, $color]
} }
}) })
@ -450,7 +461,10 @@ unsafe fn to_objc(obj: &Color) -> id {
match obj { match obj {
// Regardless of platform, we can just dereference this one. // Regardless of platform, we can just dereference this one.
Color::Object(obj) => msg_send![&**obj, self], Color::Custom(color) => {
let mut ptr = color.write().unwrap();
&mut **ptr
},
Color::SystemBlack => msg_send![color, blackColor], Color::SystemBlack => msg_send![color, blackColor],
Color::SystemWhite => msg_send![color, whiteColor], Color::SystemWhite => msg_send![color, whiteColor],
@ -488,7 +502,11 @@ unsafe fn to_objc(obj: &Color) -> id {
Color::Link => system_color_with_fallback!(color, linkColor, blueColor), Color::Link => system_color_with_fallback!(color, linkColor, blueColor),
Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor), Color::DarkText => system_color_with_fallback!(color, darkTextColor, blackColor),
Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor), Color::LightText => system_color_with_fallback!(color, lightTextColor, whiteColor),
Color::WindowBackgroundColor => system_color_with_fallback!(color, windowBackgroundColor, clearColor),
Color::UnderPageBackgroundColor => system_color_with_fallback!(color, underPageBackgroundColor, clearColor), #[cfg(feature = "macos")]
Color::MacOSWindowBackgroundColor => system_color_with_fallback!(color, windowBackgroundColor, clearColor),
#[cfg(feature = "macos")]
Color::MacOSUnderPageBackgroundColor => system_color_with_fallback!(color, underPageBackgroundColor, clearColor),
} }
} }

View file

@ -10,6 +10,7 @@ use crate::foundation::NSUInteger;
use crate::pasteboard::Pasteboard; use crate::pasteboard::Pasteboard;
/// Represents operations that can happen for a given drag/drop scenario. /// Represents operations that can happen for a given drag/drop scenario.
#[derive(Copy, Clone, Debug)]
pub enum DragOperation { pub enum DragOperation {
/// No drag operations are allowed. /// No drag operations are allowed.
None, None,
@ -53,6 +54,7 @@ impl From<DragOperation> for NSUInteger {
/// A wrapper for `NSDraggingInfo`. As this is a protocol/type you should never create yourself, /// A wrapper for `NSDraggingInfo`. As this is a protocol/type you should never create yourself,
/// this only provides getters - merely a Rust-y way to grab what you need. /// this only provides getters - merely a Rust-y way to grab what you need.
#[derive(Clone, Debug)]
pub struct DragInfo { pub struct DragInfo {
pub info: ShareId<Object> pub info: ShareId<Object>
} }

View file

@ -36,6 +36,7 @@ impl From<&EventModifierFlag> for NSUInteger {
} }
} }
#[derive(Clone, Copy, Debug)]
pub enum EventType { pub enum EventType {
KeyDown KeyDown
} }

View file

@ -2,6 +2,7 @@
use crate::foundation::{NSInteger, NSUInteger}; use crate::foundation::{NSInteger, NSUInteger};
#[derive(Copy, Clone, Debug)]
pub enum ModalResponse { pub enum ModalResponse {
Ok, Ok,
Continue, Continue,
@ -33,6 +34,7 @@ impl From<NSInteger> for ModalResponse {
} }
} }
#[derive(Copy, Clone, Debug)]
pub enum SearchPathDomainMask { pub enum SearchPathDomainMask {
User, User,
Local, Local,
@ -53,6 +55,7 @@ impl From<SearchPathDomainMask> for NSUInteger {
} }
} }
#[derive(Copy, Clone, Debug)]
pub enum SearchPathDirectory { pub enum SearchPathDirectory {
Applications, Applications,
DemoApplications, DemoApplications,

View file

@ -13,6 +13,7 @@ use crate::foundation::{id, nil, NO, NSString, NSUInteger};
use crate::error::{Error as AppKitError}; use crate::error::{Error as AppKitError};
use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask}; use crate::filesystem::enums::{SearchPathDirectory, SearchPathDomainMask};
#[derive(Debug)]
pub struct FileManager { pub struct FileManager {
pub manager: RwLock<Id<Object>> pub manager: RwLock<Id<Object>>
} }

View file

@ -7,6 +7,7 @@ use objc_id::Id;
/// ///
/// When this is dropped, we automatically send a `drain` message to the underlying pool. You can /// When this is dropped, we automatically send a `drain` message to the underlying pool. You can
/// also call `drain()` yourself if you need to drain for whatever reason. /// also call `drain()` yourself if you need to drain for whatever reason.
#[derive(Debug)]
pub struct AutoReleasePool(pub Id<Object>); pub struct AutoReleasePool(pub Id<Object>);
impl AutoReleasePool { impl AutoReleasePool {

View file

@ -95,11 +95,10 @@ impl ImageView {
} }
/// 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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.into_platform_specific_color(); let cg = color.as_ref().cg_color();
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor:cg];
} }

View file

@ -204,11 +204,11 @@ impl<T> TextField<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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.to_objc(); // @TODO: This is wrong.
let cg = color.as_ref().cg_color();
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor:cg];
} }
@ -230,9 +230,12 @@ impl<T> TextField<T> {
} }
} }
pub fn set_font(&self, font: &Font) { /// Sets the font for this input.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
let font = font.as_ref();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font.objc]; let _: () = msg_send![&*self.objc, setFont:&*font];
} }
} }
} }

View file

@ -1,5 +1,5 @@
//#![deny(missing_docs)] //#![deny(missing_docs)]
//#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] #![cfg_attr(debug_assertions, allow(dead_code, unused_imports))]
#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))] #![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
@ -74,6 +74,8 @@
//! //!
//! - `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit //! - `cloudkit`: Links `CloudKit.framework` and provides some wrappers around CloudKit
//! functionality. Currently not feature complete. //! functionality. Currently not feature complete.
//! - `color_fallbacks`: Provides fallback colors for older systems where `systemColor` types don't
//! exist. This feature is very uncommon and you probably don't need it.
//! - `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for //! - `quicklook`: Links `QuickLook.framework` and offers methods for generating preview images for
//! files. //! files.
//! - `user-notifications`: Links `UserNotifications.framework` and provides functionality for //! - `user-notifications`: Links `UserNotifications.framework` and provides functionality for

View file

@ -84,8 +84,8 @@ impl RowAction {
} }
/// Sets the background color of this action. /// Sets the background color of this action.
pub fn set_background_color(&mut self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&mut self, color: C) {
let color = color.to_objc(); let color: id = color.as_ref().into();
unsafe { unsafe {
let _: () = msg_send![&*self.0, setBackgroundColor:color]; let _: () = msg_send![&*self.0, setBackgroundColor:color];

View file

@ -3,6 +3,7 @@ use crate::foundation::{NSInteger, NSUInteger};
/// This enum represents the different stock animations possible /// This enum represents the different stock animations possible
/// for ListView row operations. You can pass it to `insert_rows` /// for ListView row operations. You can pass it to `insert_rows`
/// and `remove_rows` - reloads don't get animations. /// and `remove_rows` - reloads don't get animations.
#[derive(Copy, Clone, Debug)]
pub enum RowAnimation { pub enum RowAnimation {
/// No animation. /// No animation.
None, None,
@ -41,7 +42,7 @@ impl Into<NSUInteger> for RowAnimation {
} }
} }
#[derive(Debug)] #[derive(Copy, Clone, Debug)]
pub enum RowEdge { pub enum RowEdge {
Leading, Leading,
Trailing Trailing

View file

@ -420,9 +420,9 @@ impl<T> ListView<T> {
/// Dequeue a reusable cell. If one is not in the queue, will create and cache one for reuse. /// Dequeue a reusable cell. If one is not in the queue, will create and cache one for reuse.
pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> { pub fn dequeue<R: ViewDelegate + 'static>(&self, identifier: &'static str) -> ListViewRow<R> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
unsafe { {
let key = NSString::new(identifier); let key = NSString::new(identifier);
let cell: id = msg_send![&*self.objc, makeViewWithIdentifier:&*key owner:nil]; let cell: id = unsafe { msg_send![&*self.objc, makeViewWithIdentifier:&*key owner:nil] };
if cell != nil { if cell != nil {
ListViewRow::from_cached(cell) ListViewRow::from_cached(cell)
@ -436,13 +436,13 @@ 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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.into_platform_specific_color(); // @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor:color];
} }
} }

View file

@ -274,11 +274,12 @@ impl<T> ListViewRow<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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let mut objc = self.objc.borrow_mut(); let mut objc = self.objc.borrow_mut();
let color: id = color.as_ref().into();
unsafe { unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc()); (&mut **objc).set_ivar(BACKGROUND_COLOR, color);
} }
} }

View file

@ -34,7 +34,9 @@
//! Certain lifecycle events are specific to certain platforms. Where this is the case, the //! Certain lifecycle events are specific to certain platforms. Where this is the case, the
//! documentation makes every effort to note. //! documentation makes every effort to note.
use std::fmt;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use objc_id::Id; use objc_id::Id;
@ -51,7 +53,7 @@ mod class;
use class::register_app_class; use class::register_app_class;
mod delegate; mod delegate;
use delegate::{register_app_delegate_class}; use delegate::register_app_delegate_class;
mod enums; mod enums;
pub use enums::*; pub use enums::*;
@ -61,31 +63,6 @@ pub use traits::AppDelegate;
pub(crate) static APP_PTR: &str = "rstAppPtr"; pub(crate) static APP_PTR: &str = "rstAppPtr";
// Alright, so this... sucks.
//
// But let me explain.
//
// macOS only has one top level menu, and it's probably one of the old(est|er)
// parts of the system - there's still Carbon there, if you know where to look.
// We store our event handlers on the Rust side, and in most cases this works fine -
// we can enforce that the programmer should be retaining ownership and dropping
// when ready.
//
// We can't enforce this with NSMenu, as it takes ownership of the items and then
// lives in its own world. We want to mirror the same contract, somehow... so, again,
// we go back to the "one top level menu" bit.
//
// Yes, all this to say, this is a singleton that caches TargetActionHandler entries
// when menus are constructed. It's mostly 1:1 with the NSMenu, I think - and probably
// the only true singleton I want to see in this framework.
//
// Calls to App::set_menu() will reconfigure the contents of this Vec, so that's also
// the easiest way to remove things as necessary - just rebuild your menu. Trying to debug
// and/or predict NSMenu is a whole task that I'm not even sure is worth trying to do.
/*lazy_static! {
static ref MENU_ITEMS_HANDLER_CACHE: Arc<Mutex<Vec<TargetActionHandler>>> = Arc::new(Mutex::new(Vec::new()));
}*/
/// A handler to make some boilerplate less annoying. /// A handler to make some boilerplate less annoying.
#[inline] #[inline]
fn shared_application<F: Fn(id)>(handler: F) { fn shared_application<F: Fn(id)>(handler: F) {
@ -106,13 +83,26 @@ fn shared_application<F: Fn(id)>(handler: F) {
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the /// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
/// application. /// application.
pub struct App<T = (), M = ()> { pub struct App<T = (), M = ()> {
pub inner: Id<Object>, pub objc: Id<Object>,
pub objc_delegate: Id<Object>, pub objc_delegate: Id<Object>,
pub delegate: Box<T>, pub delegate: Box<T>,
pub pool: AutoReleasePool, pub pool: AutoReleasePool,
_message: std::marker::PhantomData<M> _message: std::marker::PhantomData<M>
} }
impl<T, M> fmt::Debug for App<T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let delegate = format!("{:p}", self.delegate);
f.debug_struct("App")
.field("objc", &self.objc)
.field("objc_delegate", &self.objc_delegate)
.field("delegate", &delegate)
.field("pool", &self.pool)
.finish()
}
}
impl<T> App<T> { impl<T> App<T> {
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called. /// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
/// If you're wondering where to go from here... you need an `AppDelegate` that implements /// If you're wondering where to go from here... you need an `AppDelegate` that implements
@ -138,7 +128,7 @@ impl<T> App<T> where T: AppDelegate + 'static {
let pool = AutoReleasePool::new(); let pool = AutoReleasePool::new();
let inner = unsafe { let objc = unsafe {
let app: id = msg_send![register_app_class(), sharedApplication]; let app: id = msg_send![register_app_class(), sharedApplication];
Id::from_ptr(app) Id::from_ptr(app)
}; };
@ -150,15 +140,15 @@ impl<T> App<T> where T: AppDelegate + 'static {
let delegate: id = msg_send![delegate_class, new]; let delegate: id = msg_send![delegate_class, new];
let delegate_ptr: *const T = &*app_delegate; let delegate_ptr: *const T = &*app_delegate;
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize); (&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
let _: () = msg_send![&*inner, setDelegate:delegate]; let _: () = msg_send![&*objc, setDelegate:delegate];
Id::from_ptr(delegate) Id::from_ptr(delegate)
}; };
App { App {
objc_delegate: objc_delegate, objc,
inner: inner, objc_delegate,
delegate: app_delegate, delegate: app_delegate,
pool: pool, pool,
_message: std::marker::PhantomData _message: std::marker::PhantomData
} }
} }

View file

@ -2,6 +2,7 @@
//! one level deep; this could change in the future but is fine for //! one level deep; this could change in the future but is fine for
//! now. //! now.
use std::fmt;
use std::sync::Once; use std::sync::Once;
use block::ConcreteBlock; use block::ConcreteBlock;
@ -25,6 +26,16 @@ static BLOCK_PTR: &'static str = "cacaoMenuItemBlockPtr";
/// a better way to do this that doesn't require double-boxing, I'm all ears. /// a better way to do this that doesn't require double-boxing, I'm all ears.
pub struct Action(Box<dyn Fn() + 'static>); pub struct Action(Box<dyn Fn() + 'static>);
impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ptr = format!("{:p}", self.0);
f.debug_struct("Action")
.field("fn", &ptr)
.finish()
}
}
/// Internal method (shorthand) for generating `NSMenuItem` holders. /// Internal method (shorthand) for generating `NSMenuItem` holders.
fn make_menu_item<S: AsRef<str>>( fn make_menu_item<S: AsRef<str>>(
title: S, title: S,

View file

@ -3,6 +3,8 @@
//! //!
//! UNFORTUNATELY, this is a very old and janky API. So... yeah. //! UNFORTUNATELY, this is a very old and janky API. So... yeah.
use std::fmt;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::ShareId;
@ -32,6 +34,7 @@ pub struct Toolbar<T = ()> {
/// The Objective-C runtime toolbar. /// The Objective-C runtime toolbar.
pub objc: ShareId<Object>, pub objc: ShareId<Object>,
/// A pointer to the underlying delegate.
pub objc_delegate: ShareId<Object>, pub objc_delegate: ShareId<Object>,
/// The user supplied delegate. /// The user supplied delegate.
@ -125,6 +128,22 @@ impl<T> Toolbar<T> {
} }
} }
impl<T> fmt::Debug for Toolbar<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let delegate = match &self.delegate {
Some(d) => format!("Some({:p})", d),
None => "None".to_string()
};
f.debug_struct("Toolbar")
.field("identifier", &self.identifier)
.field("objc", &self.objc)
.field("objc_delegate", &self.objc_delegate)
.field("delegate", &delegate)
.finish()
}
}
impl<T> Drop for Toolbar<T> { impl<T> Drop for Toolbar<T> {
/// A bit of extra cleanup for the delegate system. If we have a non-`None` delegate, this is /// A bit of extra cleanup for the delegate system. If we have a non-`None` delegate, this is
/// the OG Toolbar and should be cleaned up for any possible cyclical references. /// the OG Toolbar and should be cleaned up for any possible cyclical references.

View file

@ -27,9 +27,11 @@
//! } //! }
//! ``` //! ```
use std::fmt;
use objc::runtime::Object; use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId; use objc_id::Id;
use crate::foundation::{id, nil}; use crate::foundation::{id, nil};
use crate::utils::Controller; use crate::utils::Controller;
@ -42,7 +44,7 @@ use class::register_window_controller_class;
/// provides some extra lifecycle methods. /// provides some extra lifecycle methods.
pub struct WindowController<T> { pub struct WindowController<T> {
/// A handler to the underlying `NSWindowController`. /// A handler to the underlying `NSWindowController`.
pub objc: ShareId<Object>, pub objc: Id<Object>,
/// The underlying `Window` that this controller wraps. /// The underlying `Window` that this controller wraps.
pub window: Window<T> pub window: Window<T>
@ -64,7 +66,7 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
(&mut *controller).set_ivar(WINDOW_DELEGATE_PTR, ptr as usize); (&mut *controller).set_ivar(WINDOW_DELEGATE_PTR, ptr as usize);
} }
ShareId::from_ptr(controller) Id::from_ptr(controller)
}; };
if let Some(delegate) = &mut window.delegate { if let Some(delegate) = &mut window.delegate {
@ -74,10 +76,7 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
}); });
} }
WindowController { WindowController { objc, window }
objc: objc,
window: window
}
} }
/// 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.
@ -103,3 +102,11 @@ impl<T> WindowController<T> where T: WindowDelegate + 'static {
} }
} }
} }
impl<T> fmt::Debug for WindowController<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowController")
.field("objc", &self.objc)
.finish()
}
}

View file

@ -3,6 +3,7 @@
use crate::foundation::{NSInteger, NSUInteger}; use crate::foundation::{NSInteger, NSUInteger};
/// Describes window styles that can be displayed. /// Describes window styles that can be displayed.
#[derive(Clone, Copy, Debug)]
pub enum WindowStyle { pub enum WindowStyle {
/// Window has no border. You generally do not want this. /// Window has no border. You generally do not want this.
Borderless, Borderless,
@ -80,6 +81,7 @@ impl From<&WindowStyle> for NSUInteger {
} }
/// Describes whether a window shows a title or not. /// Describes whether a window shows a title or not.
#[derive(Clone, Copy, Debug)]
pub enum TitleVisibility { pub enum TitleVisibility {
/// Title is visible. /// Title is visible.
Visible, Visible,

View file

@ -330,9 +330,11 @@ impl<T> Window<T> {
} }
/// Sets the background color for the window. You generally don't want to do this often. /// Sets the background color for the window. You generally don't want to do this often.
pub fn set_background_color(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let color: id = color.as_ref().into();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setBackgroundColor:color.into_platform_specific_color()]; let _: () = msg_send![&*self.objc, setBackgroundColor:color];
} }
} }

View file

@ -16,6 +16,7 @@ mod types;
pub use types::{PasteboardName, PasteboardType}; pub use types::{PasteboardName, PasteboardType};
/// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop. /// Represents an `NSPasteboard`, enabling you to handle copy/paste/drag and drop.
#[derive(Debug)]
pub struct Pasteboard(pub ShareId<Object>); pub struct Pasteboard(pub ShareId<Object>);
impl Default for Pasteboard { impl Default for Pasteboard {

View file

@ -85,8 +85,8 @@ impl ProgressIndicator {
} }
impl ProgressIndicator { impl ProgressIndicator {
/// 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(&self, color: Color) { /*pub fn set_background_color(&self, color: Color) {
let bg = color.into_platform_specific_color(); let bg = color.into_platform_specific_color();
unsafe { unsafe {
@ -94,7 +94,7 @@ impl ProgressIndicator {
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor:cg];
} }
} }*/
/// Starts the animation for an indeterminate indicator. /// Starts the animation for an indeterminate indicator.
pub fn start_animation(&self) { pub fn start_animation(&self) {

View file

@ -154,8 +154,6 @@ impl<T> ScrollView<T> where T: ScrollViewDelegate + 'static {
let view = allocate_view(register_scrollview_class_with_delegate::<T>); let view = allocate_view(register_scrollview_class_with_delegate::<T>);
unsafe { unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let ptr: *const T = &*delegate; let ptr: *const T = &*delegate;
(&mut *view).set_ivar(SCROLLVIEW_DELEGATE_PTR, ptr as usize); (&mut *view).set_ivar(SCROLLVIEW_DELEGATE_PTR, ptr as usize);
}; };
@ -200,13 +198,13 @@ 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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.into_platform_specific_color(); // @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor:color];
} }
} }
} }

View file

@ -0,0 +1,102 @@
use std::{fmt, slice, str};
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, Range};
use std::os::raw::c_char;
use core_foundation::base::CFRange;
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::Id;
use crate::color::Color;
use crate::foundation::{id, to_bool, BOOL, YES, NO, NSString};
extern "C" {
static NSForegroundColorAttributeName: id;
}
/// A wrapper around `NSMutableAttributedString`, which can be used for more complex text
/// rendering.
///
pub struct AttributedString(pub Id<Object>);
impl AttributedString {
/// Creates a blank AttributedString. Internally, this allocates an
/// `NSMutableAttributedString`, which is required for controls to make use of rich text.
pub fn new(value: &str) -> Self {
let text = NSString::no_copy(value);
Self(unsafe {
let alloc: id = msg_send![class!(NSMutableAttributedString), alloc];
Id::from_ptr(msg_send![alloc, initWithString:&*text])
})
}
/// Creates a mutableCopy of a passed in `NSAttributedString` instance. This is mostly for
/// internal use, but kept available as part of the public API for the more adventurous types
/// who might need it.
pub fn wrap(value: id) -> Self {
Self(unsafe {
Id::from_ptr(msg_send![value, mutableCopy])
})
}
/// Sets the text (foreground) color for the specified range.
pub fn set_text_color<C: AsRef<Color>>(&mut self, color: C, range: Range<isize>) {
let color: id = color.as_ref().into();
let range = CFRange::init(range.start, range.end);
unsafe {
let _: () = msg_send![&*self.0, addAttribute:NSForegroundColorAttributeName
value:color
range:range
];
}
}
}
impl fmt::Display for AttributedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let string = NSString::from_retained(unsafe {
msg_send![&*self.0, string]
});
write!(f, "{}", string.to_str())
}
}
impl fmt::Debug for AttributedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let string = NSString::from_retained(unsafe {
msg_send![&*self.0, string]
});
f.debug_struct("AttributedString")
.field("text", &string.to_str())
.finish()
}
}
impl From<AttributedString> for id {
/// Consumes and returns the pointer to the underlying NSMutableAttributedString instance.
fn from(mut string: AttributedString) -> Self {
&mut *string.0
}
}
impl Deref for AttributedString {
type Target = Object;
/// Derefs to the underlying Objective-C Object.
fn deref(&self) -> &Object {
&*self.0
}
}
impl DerefMut for AttributedString {
/// Derefs to the underlying Objective-C Object.
fn deref_mut(&mut self) -> &mut Object {
&mut *self.0
}
}

View file

@ -1,11 +1,21 @@
use crate::foundation::{NSInteger, NSUInteger}; use crate::foundation::{NSInteger, NSUInteger};
/// Specifies how text should align for a supported control.
#[derive(Copy, Clone, Debug)]
pub enum TextAlign { pub enum TextAlign {
/// Align text to the left.
Left, Left,
/// Align text to the right.
Right, Right,
/// Center-align text.
Center, Center,
/// Justify text.
Justified, Justified,
/// Natural.
Natural Natural
} }
@ -22,6 +32,7 @@ impl From<TextAlign> for NSInteger {
} }
/// Instructs text controls how to optimize line breaks. /// Instructs text controls how to optimize line breaks.
#[derive(Copy, Clone, Debug)]
pub enum LineBreakMode { pub enum LineBreakMode {
/// Wrap at word boundaries (the default) /// Wrap at word boundaries (the default)
WrapWords, WrapWords,

View file

@ -1,5 +1,7 @@
//! Implements `Font`, a wrapper around `NSFont` on macOS and `UIFont` on iOS. //! Implements `Font`, a wrapper around `NSFont` on macOS and `UIFont` on iOS.
use std::ops::Deref;
use core_graphics::base::CGFloat; use core_graphics::base::CGFloat;
use objc_id::ShareId; use objc_id::ShareId;
@ -8,37 +10,55 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSArray, NSString}; use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
#[derive(Debug)] /// A `Font` can be constructed and applied to supported controls to control things like text
pub struct Font { /// appearance and size.
pub objc: ShareId<Object> #[derive(Clone, Debug)]
} pub struct Font(pub ShareId<Object>);
impl Default for Font { impl Default for Font {
/// Returns the default `labelFont` on macOS.
fn default() -> Self { fn default() -> Self {
Font { Font(unsafe {
objc: unsafe { let cls = class!(NSFont);
let cls = class!(NSFont); let default_size: id = msg_send![cls, labelFontSize];
let default_size: id = msg_send![cls, labelFontSize]; ShareId::from_ptr(msg_send![cls, labelFontOfSize:default_size])
ShareId::from_ptr(msg_send![cls, labelFontOfSize:default_size]) })
}
}
} }
} }
impl Font { impl Font {
pub fn system(size: CGFloat) -> Self { /// Creates and returns a default system font at the specified size.
Font { pub fn system(size: f64) -> Self {
objc: unsafe { let size = size as CGFloat;
ShareId::from_ptr(msg_send![class!(NSFont), systemFontOfSize:size])
} Font(unsafe {
} ShareId::from_ptr(msg_send![class!(NSFont), systemFontOfSize:size])
})
} }
pub fn bold_system(size: CGFloat) -> Self { /// Creates and returns a default bold system font at the specified size.
Font { pub fn bold_system(size: f64) -> Self {
objc: unsafe { let size = size as CGFloat;
ShareId::from_ptr(msg_send![class!(NSFont), boldSystemFontOfSize:size])
} Font(unsafe {
} ShareId::from_ptr(msg_send![class!(NSFont), boldSystemFontOfSize:size])
})
}
}
impl Deref for Font {
type Target = Object;
/// Derefs to the underlying Objective-C Object.
fn deref(&self) -> &Object {
&*self.0
}
}
impl AsRef<Font> for Font {
/// Provided to make passing `Font` types around less of a headache.
#[inline]
fn as_ref(&self) -> &Font {
self
} }
} }

View file

@ -203,19 +203,19 @@ 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(&self, color: Color) { pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
let bg = color.into_platform_specific_color(); // @TODO: This is wrong.
let color = color.as_ref().cg_color();
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; 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(&self, color: Color) { pub fn set_text_color<C: AsRef<Color>>(&self, color: C) {
let color = color.into_platform_specific_color(); let color: id = color.as_ref().into();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setTextColor:color]; let _: () = msg_send![&*self.objc, setTextColor:color];
@ -240,6 +240,7 @@ impl<T> Label<T> {
s.to_string() s.to_string()
} }
/// Sets the text alignment for this label.
pub fn set_text_alignment(&self, alignment: TextAlign) { pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe { unsafe {
let alignment: NSInteger = alignment.into(); let alignment: NSInteger = alignment.into();
@ -247,12 +248,18 @@ impl<T> Label<T> {
} }
} }
pub fn set_font(&self, font: &Font) { /// Sets the font for this label.
pub fn set_font<F: AsRef<Font>>(&self, font: F) {
// This clone is here to ensure there's no oddities with retain counts on the underlying
// font object - it seems like it can be optimized away otherwise.
let font = font.as_ref().clone();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setFont:&*font.objc]; let _: () = msg_send![&*self.objc, setFont:&*font];
} }
} }
/// Set whether this is hidden or not.
pub fn set_hidden(&self, hidden: bool) { pub fn set_hidden(&self, hidden: bool) {
unsafe { unsafe {
let _: () = msg_send![&*self.objc, setHidden:match hidden { let _: () = msg_send![&*self.objc, setHidden:match hidden {
@ -262,6 +269,7 @@ impl<T> Label<T> {
} }
} }
/// 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 { unsafe {

View file

@ -1,11 +1,14 @@
//! The `text` module encompasses various widgets for rendering and interacting //! The `text` module encompasses various widgets for rendering and interacting
//! with text. //! with text.
pub mod label; mod attributed_string;
pub use attributed_string::AttributedString;
mod label;
pub use label::Label; pub use label::Label;
pub mod enums; mod enums;
pub use enums::{LineBreakMode, TextAlign}; pub use enums::{LineBreakMode, TextAlign};
pub mod font; mod font;
pub use font::Font; pub use font::Font;

View file

@ -1,21 +1,22 @@
//! A module wrapping `NSUserActivity`. //! A module wrapping `NSUserActivity`.
//!
//! This is primarily used in handling app handoff between devices.
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::ShareId;
use crate::foundation::id; use crate::foundation::id;
/// Represents an `NSUserActivity`, which acts as a lightweight method to capture the state of your /// Represents an `NSUserActivity`, which acts as a lightweight method to capture
/// app. /// the state of your app.
pub struct UserActivity { #[derive(Debug)]
pub inner: ShareId<Object> pub struct UserActivity(pub ShareId<Object>);
}
impl UserActivity { impl UserActivity {
/// An internal method for wrapping a system-provided activity. /// An internal method for wrapping a system-provided activity.
pub(crate) fn with_inner(object: id) -> Self { pub(crate) fn with_inner(object: id) -> Self {
UserActivity { UserActivity(unsafe {
inner: unsafe { ShareId::from_ptr(object) } ShareId::from_ptr(object)
} })
} }
} }

View file

@ -12,36 +12,12 @@ use objc_id::ShareId;
use crate::foundation::{id, BOOL, YES, NO}; use crate::foundation::{id, BOOL, YES, NO};
pub mod os { pub mod os;
use lazy_static::lazy_static;
use os_info::Version;
lazy_static! {
pub static ref OS_VERSION: os_info::Info = os_info::get();
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_version(minimum_major: u64) -> bool {
match OS_VERSION.version() {
Version::Semantic(os_major, _, _) => { *os_major >= minimum_major },
_ => false
}
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_semversion(major: u64, minor: u64, patch: u64) -> bool {
let target = Version::Semantic(major, minor, patch);
OS_VERSION.version() > &target
}
}
/// 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|etc)Controller. Only needs to return the backing node. /// a guard for whether something is a (View|Window|etc)Controller.
pub trait Controller { pub trait Controller {
/// Returns the underlying Objective-C object.
fn get_backing_node(&self) -> ShareId<Object>; fn get_backing_node(&self) -> ShareId<Object>;
} }
@ -69,6 +45,7 @@ pub fn load<'a, T>(this: &'a Object, ptr_name: &str) -> &'a T {
} }
} }
/// Asynchronously execute a callback on the main thread via Grand Central Dispatch.
pub fn async_main_thread<F>(method: F) pub fn async_main_thread<F>(method: F)
where where
F: Fn() + Send + 'static F: Fn() + Send + 'static
@ -77,6 +54,7 @@ where
queue.exec_async(method); queue.exec_async(method);
} }
/// Synchronously execute a callback on the main thread via Grand Central Dispatch.
pub fn sync_main_thread<F>(method: F) pub fn sync_main_thread<F>(method: F)
where where
F: Fn() + Send + 'static F: Fn() + Send + 'static
@ -90,21 +68,27 @@ where
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct CGSize { pub struct CGSize {
/// The width of this size.
pub width: CGFloat, pub width: CGFloat,
/// The height of this size.
pub height: CGFloat, pub height: CGFloat,
} }
impl CGSize { impl CGSize {
/// Create and return a new `CGSize`.
pub fn new(width: CGFloat, height: CGFloat) -> Self { pub fn new(width: CGFloat, height: CGFloat) -> Self {
CGSize { width, height } CGSize { width, height }
} }
/// Create and return a `CGSizeZero` equivalent.
pub fn zero() -> Self { pub fn zero() -> Self {
CGSize { width: 0., height: 0. } CGSize { width: 0., height: 0. }
} }
} }
unsafe impl Encode for CGSize { unsafe impl Encode for CGSize {
/// Adds support for CGSize Objective-C encoding.
fn encode() -> Encoding { fn encode() -> Encoding {
let encoding = format!("{{CGSize={}{}}}", let encoding = format!("{{CGSize={}{}}}",
CGFloat::encode().as_str(), CGFloat::encode().as_str(),

27
src/utils/os.rs Normal file
View file

@ -0,0 +1,27 @@
//! Helper methods for OS version checking.
use lazy_static::lazy_static;
use os_info::Version;
lazy_static! {
/// A cached struct containing OS version for runtime checks.
pub static ref OS_VERSION: os_info::Info = os_info::get();
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_version(minimum_major: u64) -> bool {
match OS_VERSION.version() {
Version::Semantic(os_major, _, _) => { *os_major >= minimum_major },
_ => false
}
}
/// In rare cases we need to check whether something is a specific version of macOS. This is a
/// runtime check thhat returns a boolean indicating whether the current version is a minimum target.
#[inline(always)]
pub fn is_minimum_semversion(major: u64, minor: u64, patch: u64) -> bool {
let target = Version::Semantic(major, minor, patch);
OS_VERSION.version() > &target
}

View file

@ -3,7 +3,7 @@ use objc::runtime::Object;
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use crate::foundation::id; use crate::foundation::id;
use crate::layout::{Layout}; use crate::layout::Layout;
use crate::view::{VIEW_DELEGATE_PTR, View, ViewDelegate}; use crate::view::{VIEW_DELEGATE_PTR, View, ViewDelegate};
use crate::utils::Controller; use crate::utils::Controller;
@ -19,13 +19,38 @@ mod ios;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
use ios::register_view_controller_class; use ios::register_view_controller_class;
/// A `ViewController` is a wrapper around `NSViewController` on macOS, and `UIViewController` on
/// iOS and tvOS.
///
/// This type is interchangeable with a standard `View<T>`, in that using this simply forwards
/// standard view controller lifecycle methods onto your `ViewDelegate`. You would use this if you
/// need to be notified of _when_ something is going to be used (e.g, for lifecycle event-based
/// cleanup routines, or something).
///
/// ## Example
/// ```rust,no_run
/// struct ContentViewDelegate;
///
/// impl ViewDelegate for ContentViewDelegate {
/// fn will_appear(&self, animated: bool) {
/// println!("This controller is about to appear!");
/// }
/// }
/// ```
#[derive(Debug)] #[derive(Debug)]
pub struct ViewController<T> { pub struct ViewController<T> {
/// The underlying Objective-C pointer.
pub objc: ShareId<Object>, pub objc: ShareId<Object>,
/// The underlying View that we manage.
pub view: View<T> pub view: View<T>
} }
impl<T> ViewController<T> where T: ViewDelegate + 'static { impl<T> ViewController<T>
where
T: ViewDelegate + 'static
{
/// Creates and returns a new `ViewController` with the provided `delegate`.
pub fn new(delegate: T) -> Self { pub fn new(delegate: T) -> Self {
let class = register_view_controller_class::<T>(&delegate); let class = register_view_controller_class::<T>(&delegate);
let view = View::with(delegate); let view = View::with(delegate);
@ -43,10 +68,7 @@ impl<T> ViewController<T> where T: ViewDelegate + 'static {
ShareId::from_ptr(vc) ShareId::from_ptr(vc)
}; };
ViewController { ViewController { objc, view }
objc: objc,
view: view
}
} }
} }

View file

@ -209,9 +209,10 @@ impl<T> View<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) {
let mut objc = self.objc.borrow_mut(); let mut objc = self.objc.borrow_mut();
let color: id = color.as_ref().into();
unsafe { unsafe {
(&mut **objc).set_ivar(BACKGROUND_COLOR, color.as_ref().to_objc()); (&mut **objc).set_ivar(BACKGROUND_COLOR, color);
} }
} }

View file

@ -3,6 +3,12 @@
use crate::dragdrop::{DragInfo, DragOperation}; use crate::dragdrop::{DragInfo, DragOperation};
use crate::view::View; use crate::view::View;
/// This trait can be used for implementing custom View behavior. You implement this trait on your
/// struct, and wrap your struct in a `View` or `ViewController`. The view or controller then
/// handles interfacing between your struct and system events.
///
/// It winds up feeling to subclassing, without the ability to subclass multiple levels deep and
/// get ultra confusing.
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait ViewDelegate { pub trait ViewDelegate {
/// Used to cache subclass creations on the Objective-C side. /// Used to cache subclass creations on the Objective-C side.
@ -33,20 +39,24 @@ pub trait ViewDelegate {
/// Called when this has been removed from the view heirarchy. /// Called when this has been removed from the view heirarchy.
fn did_disappear(&self, animated: bool) {} fn did_disappear(&self, animated: bool) {}
/// Invoked when the dragged image enters destination bounds or frame; returns dragging operation to perform. /// Invoked when the dragged image enters destination bounds or frame; returns dragging
/// operation to perform.
fn dragging_entered(&self, info: DragInfo) -> DragOperation { DragOperation::None } fn dragging_entered(&self, info: DragInfo) -> DragOperation { DragOperation::None }
/// Invoked when the image is released, allowing the receiver to agree to or refuse drag operation. /// Invoked when the image is released, allowing the receiver to agree to or refuse
/// drag operation.
fn prepare_for_drag_operation(&self, info: DragInfo) -> bool { false } fn prepare_for_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked after the released image has been removed from the screen, signaling the receiver to import the pasteboard data. /// Invoked after the released image has been removed from the screen, signaling the
/// receiver to import the pasteboard data.
fn perform_drag_operation(&self, info: DragInfo) -> bool { false } fn perform_drag_operation(&self, info: DragInfo) -> bool { false }
/// Invoked when the dragging operation is complete, signaling the receiver to perform any necessary clean-up. /// Invoked when the dragging operation is complete, signaling the receiver to perform
/// any necessary clean-up.
fn conclude_drag_operation(&self, info: DragInfo) {} fn conclude_drag_operation(&self, info: DragInfo) {}
/// Invoked when the dragged image exits the destinations bounds rectangle (in the case of a view) or its frame /// Invoked when the dragged image exits the destinations bounds rectangle (in the case
/// rectangle (in the case of a window object). /// of a view) or its frame rectangle (in the case of a window object).
fn dragging_exited(&self, info: DragInfo) {} fn dragging_exited(&self, info: DragInfo) {}
//fn perform_key_equivalent(&self, event: Event) -> bool { false } //fn perform_key_equivalent(&self, event: Event) -> bool { false }