Add in .layer
support for Views.
- Cleans up `View` implementation so there's less boilerplate all around. - Adds in a `Layer` wrapper for `CALayer`s on widgets.
This commit is contained in:
parent
2894699ace
commit
46ee9e2ea8
|
@ -40,6 +40,7 @@ impl WindowDelegate for AppWindow {
|
||||||
});
|
});
|
||||||
|
|
||||||
self.blue.set_background_color(Color::SystemBlue);
|
self.blue.set_background_color(Color::SystemBlue);
|
||||||
|
self.blue.layer.set_corner_radius(16.);
|
||||||
self.content.add_subview(&self.blue);
|
self.content.add_subview(&self.blue);
|
||||||
|
|
||||||
self.red.set_background_color(Color::SystemRed);
|
self.red.set_background_color(Color::SystemRed);
|
||||||
|
|
|
@ -212,10 +212,10 @@ impl UserDefaults {
|
||||||
"d" => Some(Value::Float(number.as_f64())),
|
"d" => Some(Value::Float(number.as_f64())),
|
||||||
"q" => Some(Value::Integer(number.as_i64())),
|
"q" => Some(Value::Integer(number.as_i64())),
|
||||||
|
|
||||||
x => {
|
_x => {
|
||||||
// Debugging code that should be removed at some point.
|
// Debugging code that should be removed at some point.
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
println!("Unexpected code type found: {}", x);
|
println!("Unexpected code type found: {}", _x);
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
62
src/layer/mod.rs
Normal file
62
src/layer/mod.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
//! Wraps `CALayer` across all platforms.
|
||||||
|
//!
|
||||||
|
//! Each widget has an underlying `layer` field that you can access, which offers additional
|
||||||
|
//! rendering tools.
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! // Create a rounded red box
|
||||||
|
//! let view = View::default();
|
||||||
|
//! view.set_background_color(Color::SystemRed);
|
||||||
|
//! view.layer.set_corner_radius(4.0);
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use core_graphics::base::CGFloat;
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
|
|
||||||
|
use crate::foundation::id;
|
||||||
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
|
||||||
|
/// Represents a `CALayer`.
|
||||||
|
///
|
||||||
|
/// Each widget has an underlying `layer` field that you can access, which offers additional
|
||||||
|
/// rendering tools.
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// // Create a rounded red box
|
||||||
|
/// let view = View::default();
|
||||||
|
/// view.set_background_color(Color::SystemRed);
|
||||||
|
/// view.layer.set_corner_radius(4.0);
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Layer {
|
||||||
|
/// The underlying layer pointer.
|
||||||
|
pub objc: ObjcProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layer {
|
||||||
|
/// Creates a new `CALayer` and retains it.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Layer {
|
||||||
|
objc: ObjcProperty::retain(unsafe {
|
||||||
|
msg_send![class!(CALayer), new]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wraps an existing (already retained) `CALayer`.
|
||||||
|
pub fn wrap(layer: id) -> Self {
|
||||||
|
Layer {
|
||||||
|
objc: ObjcProperty::from_retained(layer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the corner radius (for all four corners).
|
||||||
|
///
|
||||||
|
/// Note that for performance sensitive contexts, you might want to apply a mask instead.
|
||||||
|
pub fn set_corner_radius(&self, radius: f64) {
|
||||||
|
self.objc.with_mut(|obj| unsafe {
|
||||||
|
let _: () = msg_send![obj, setCornerRadius:radius as CGFloat];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,6 +120,7 @@ pub mod foundation;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
pub mod layer;
|
||||||
pub(crate) mod invoker;
|
pub(crate) mod invoker;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod listview;
|
pub mod listview;
|
||||||
|
|
|
@ -46,10 +46,11 @@ use std::cell::RefCell;
|
||||||
|
|
||||||
use objc_id::{Id, ShareId};
|
use objc_id::{Id, ShareId};
|
||||||
use objc::runtime::{Class, Object};
|
use objc::runtime::{Class, Object};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
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};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
use crate::layer::Layer;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
use crate::view::ViewDelegate;
|
use crate::view::ViewDelegate;
|
||||||
|
@ -260,6 +261,8 @@ impl<T> ListViewRow<T> {
|
||||||
pub(crate) fn clone_as_handle(&self) -> crate::view::View {
|
pub(crate) fn clone_as_handle(&self) -> crate::view::View {
|
||||||
crate::view::View {
|
crate::view::View {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
|
is_handle: true,
|
||||||
|
layer: Layer::new(),
|
||||||
top: self.top.clone(),
|
top: self.top.clone(),
|
||||||
leading: self.leading.clone(),
|
leading: self.leading.clone(),
|
||||||
left: self.left.clone(),
|
left: self.left.clone(),
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//! Views implement Autolayout, which enable you to specify how things should appear on the screen.
|
//! Views implement Autolayout, which enable you to specify how things should appear on the screen.
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use cacao::color::rgb;
|
//! use cacao::color::Color;
|
||||||
//! use cacao::layout::{Layout, LayoutConstraint};
|
//! use cacao::layout::{Layout, LayoutConstraint};
|
||||||
//! use cacao::view::View;
|
//! use cacao::view::View;
|
||||||
//! use cacao::window::{Window, WindowDelegate};
|
//! use cacao::window::{Window, WindowDelegate};
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
//! window.set_minimum_content_size(300., 300.);
|
//! window.set_minimum_content_size(300., 300.);
|
||||||
//! self.window = window;
|
//! self.window = window;
|
||||||
//!
|
//!
|
||||||
//! self.red.set_background_color(rgb(224, 82, 99));
|
//! self.red.set_background_color(Color::SystemRed);
|
||||||
//! self.content.add_subview(&self.red);
|
//! self.content.add_subview(&self.red);
|
||||||
//!
|
//!
|
||||||
//! self.window.set_content_view(&self.content);
|
//! self.window.set_content_view(&self.content);
|
||||||
|
@ -41,12 +41,12 @@
|
||||||
//!
|
//!
|
||||||
//! For more information on Autolayout, view the module or check out the examples folder.
|
//! For more information on Autolayout, view the module or check out the examples folder.
|
||||||
|
|
||||||
use objc_id::{Id, ShareId};
|
|
||||||
use objc::runtime::{Class, Object};
|
use objc::runtime::{Class, Object};
|
||||||
use objc::{msg_send, sel, sel_impl};
|
use objc::{msg_send, sel, sel_impl};
|
||||||
|
|
||||||
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
|
use crate::layer::Layer;
|
||||||
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
||||||
use crate::pasteboard::PasteboardType;
|
use crate::pasteboard::PasteboardType;
|
||||||
use crate::utils::properties::ObjcProperty;
|
use crate::utils::properties::ObjcProperty;
|
||||||
|
@ -75,27 +75,23 @@ pub use traits::ViewDelegate;
|
||||||
pub(crate) static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
pub(crate) static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
||||||
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
||||||
|
|
||||||
/// A helper method for instantiating view classes and applying default settings to them.
|
|
||||||
fn common_init(class: *const Class) -> id {
|
|
||||||
unsafe {
|
|
||||||
let view: id = msg_send![class, new];
|
|
||||||
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let _: () = msg_send![view, setWantsLayer:YES];
|
|
||||||
|
|
||||||
view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
|
/// A clone-able handler to a `ViewController` reference in the Objective C runtime. We use this
|
||||||
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
|
/// instead of a stock `View` for easier recordkeeping, since it'll need to hold the `View` on that
|
||||||
/// side anyway.
|
/// side anyway.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct View<T = ()> {
|
pub struct View<T = ()> {
|
||||||
|
/// An internal flag for whether an instance of a View<T> is a handle. Typically, there's only
|
||||||
|
/// one instance that should have this set to `false` - if that one drops, we need to know to
|
||||||
|
/// do some extra cleanup.
|
||||||
|
pub is_handle: bool,
|
||||||
|
|
||||||
/// A pointer to the Objective-C runtime view controller.
|
/// A pointer to the Objective-C runtime view controller.
|
||||||
pub objc: ObjcProperty,
|
pub objc: ObjcProperty,
|
||||||
|
|
||||||
|
/// References the underlying layer. This is consistent across macOS, iOS and tvOS - on macOS
|
||||||
|
/// we explicitly opt in to layer backed views.
|
||||||
|
pub layer: Layer,
|
||||||
|
|
||||||
/// A pointer to the delegate for this view.
|
/// A pointer to the delegate for this view.
|
||||||
pub delegate: Option<Box<T>>,
|
pub delegate: Option<Box<T>>,
|
||||||
|
|
||||||
|
@ -131,17 +127,28 @@ pub struct View<T = ()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for View {
|
impl Default for View {
|
||||||
|
/// Returns a stock view, for... well, whatever you want.
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
View::new()
|
View::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
/// Returns a default `View`, suitable for
|
/// An internal initializer method for very common things that we need to do, regardless of
|
||||||
pub fn new() -> Self {
|
/// what type the end user is creating.
|
||||||
let view = common_init(register_view_class());
|
///
|
||||||
|
/// This handles grabbing autolayout anchor pointers, as well as things related to layering and
|
||||||
|
/// so on. It returns a generic `View<T>`, which the caller can then customize as needed.
|
||||||
|
pub(crate) fn init<T>(view: id) -> View<T> {
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let _: () = msg_send![view, setWantsLayer:YES];
|
||||||
|
}
|
||||||
|
|
||||||
View {
|
View {
|
||||||
|
is_handle: false,
|
||||||
delegate: None,
|
delegate: None,
|
||||||
top: LayoutAnchorY::top(view),
|
top: LayoutAnchorY::top(view),
|
||||||
left: LayoutAnchorX::left(view),
|
left: LayoutAnchorX::left(view),
|
||||||
|
@ -153,9 +160,21 @@ impl View {
|
||||||
height: LayoutAnchorDimension::height(view),
|
height: LayoutAnchorDimension::height(view),
|
||||||
center_x: LayoutAnchorX::center(view),
|
center_x: LayoutAnchorX::center(view),
|
||||||
center_y: LayoutAnchorY::center(view),
|
center_y: LayoutAnchorY::center(view),
|
||||||
|
|
||||||
|
layer: Layer::wrap(unsafe {
|
||||||
|
msg_send![view, layer]
|
||||||
|
}),
|
||||||
|
|
||||||
objc: ObjcProperty::retain(view),
|
objc: ObjcProperty::retain(view),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a default `View`, suitable for customizing and displaying.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
View::init(unsafe {
|
||||||
|
msg_send![register_view_class(), new]
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> View<T> where T: ViewDelegate + 'static {
|
impl<T> View<T> where T: ViewDelegate + 'static {
|
||||||
|
@ -166,28 +185,14 @@ impl<T> View<T> where T: ViewDelegate + 'static {
|
||||||
let mut delegate = Box::new(delegate);
|
let mut delegate = Box::new(delegate);
|
||||||
|
|
||||||
let view = unsafe {
|
let view = unsafe {
|
||||||
let view: id = common_init(class);
|
let view: id = msg_send![class, new];
|
||||||
let ptr = Box::into_raw(delegate);
|
let ptr = Box::into_raw(delegate);
|
||||||
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
|
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, ptr as usize);
|
||||||
delegate = Box::from_raw(ptr);
|
delegate = Box::from_raw(ptr);
|
||||||
view
|
view
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut view = View {
|
let mut view = View::init(view);
|
||||||
delegate: None,
|
|
||||||
top: LayoutAnchorY::top(view),
|
|
||||||
left: LayoutAnchorX::left(view),
|
|
||||||
leading: LayoutAnchorX::leading(view),
|
|
||||||
right: LayoutAnchorX::right(view),
|
|
||||||
trailing: LayoutAnchorX::trailing(view),
|
|
||||||
bottom: LayoutAnchorY::bottom(view),
|
|
||||||
width: LayoutAnchorDimension::width(view),
|
|
||||||
height: LayoutAnchorDimension::height(view),
|
|
||||||
center_x: LayoutAnchorX::center(view),
|
|
||||||
center_y: LayoutAnchorY::center(view),
|
|
||||||
objc: ObjcProperty::retain(view),
|
|
||||||
};
|
|
||||||
|
|
||||||
(&mut delegate).did_load(view.clone_as_handle());
|
(&mut delegate).did_load(view.clone_as_handle());
|
||||||
view.delegate = Some(delegate);
|
view.delegate = Some(delegate);
|
||||||
view
|
view
|
||||||
|
@ -202,6 +207,8 @@ impl<T> View<T> {
|
||||||
pub(crate) fn clone_as_handle(&self) -> View {
|
pub(crate) fn clone_as_handle(&self) -> View {
|
||||||
View {
|
View {
|
||||||
delegate: None,
|
delegate: None,
|
||||||
|
is_handle: true,
|
||||||
|
layer: self.layer.clone(),
|
||||||
top: self.top.clone(),
|
top: self.top.clone(),
|
||||||
leading: self.leading.clone(),
|
leading: self.leading.clone(),
|
||||||
left: self.left.clone(),
|
left: self.left.clone(),
|
||||||
|
@ -245,20 +252,17 @@ impl<T> Layout for View<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Drop for View<T> {
|
impl<T> Drop for View<T> {
|
||||||
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
/// If the instance being dropped is _not_ a handle, then we want to go ahead and explicitly
|
||||||
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
/// remove it from any super views.
|
||||||
/// this has a superview (i.e, it's in the heirarchy) on the AppKit side. If it does, we go
|
|
||||||
/// ahead and remove it - this is intended to match the semantics of how Rust handles things).
|
|
||||||
///
|
///
|
||||||
/// There are, thankfully, no delegates we need to break here.
|
/// Why do we do this? It's to try and match Rust's ownership model/semantics. If a Rust value
|
||||||
|
/// drops, it (theoretically) makes sense that the View would drop... and not be visible, etc.
|
||||||
|
///
|
||||||
|
/// If you're venturing into unsafe code for the sake of custom behavior via the Objective-C
|
||||||
|
/// runtime, you can consider flagging your instance as a handle - it will avoid the drop logic here.
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
/*if self.delegate.is_some() {
|
if !self.is_handle {
|
||||||
unsafe {
|
self.remove_from_superview();
|
||||||
let superview: id = msg_send![&*self.objc, superview];
|
|
||||||
if superview != nil {
|
|
||||||
let _: () = msg_send![&*self.objc, removeFromSuperview];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue