2020-03-19 20:07:44 -07:00
|
|
|
//! A `ViewHandle` represents an underlying `NSView`. You're passed a reference to one during your
|
|
|
|
//! `ViewController::did_load()` method. This method is safe to store and use, however as it's
|
|
|
|
//! UI-specific it's not thread safe.
|
|
|
|
//!
|
|
|
|
//! You can use this struct to configure how a view should look and layout. It implements
|
|
|
|
//! AutoLayout - for more information, see the AutoLayout tutorial.
|
|
|
|
|
|
|
|
use std::rc::Rc;
|
|
|
|
use std::cell::RefCell;
|
|
|
|
|
|
|
|
use objc_id::ShareId;
|
|
|
|
use objc::runtime::Object;
|
|
|
|
use objc::{msg_send, sel, sel_impl};
|
|
|
|
|
|
|
|
use crate::foundation::{id, YES, NO, NSArray, NSString};
|
|
|
|
use crate::color::Color;
|
|
|
|
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
|
|
|
|
use crate::pasteboard::PasteboardType;
|
|
|
|
|
|
|
|
mod class;
|
|
|
|
use class::{register_view_class, register_view_class_with_delegate};
|
|
|
|
|
|
|
|
pub mod controller;
|
|
|
|
pub use controller::ViewController;
|
|
|
|
|
|
|
|
pub mod traits;
|
|
|
|
pub use traits::ViewDelegate;
|
|
|
|
|
2020-03-20 15:03:19 -07:00
|
|
|
pub(crate) static VIEW_DELEGATE_PTR: &str = "rstViewDelegatePtr";
|
|
|
|
|
2020-03-19 20:07:44 -07:00
|
|
|
/// 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
|
|
|
|
/// side anyway.
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct View<T = ()> {
|
|
|
|
/// A pointer to the Objective-C runtime view controller.
|
|
|
|
pub objc: ShareId<Object>,
|
|
|
|
|
|
|
|
/// An internal callback pointer that we use in delegate loopbacks. Default implementations
|
|
|
|
/// don't require this.
|
2020-03-20 15:03:19 -07:00
|
|
|
pub(crate) internal_callback_ptr: Option<*const RefCell<T>>,
|
2020-03-19 20:07:44 -07:00
|
|
|
|
|
|
|
/// A pointer to the delegate for this view.
|
|
|
|
pub delegate: Option<Rc<RefCell<T>>>,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime top layout constraint.
|
|
|
|
pub top: LayoutAnchorY,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime leading layout constraint.
|
|
|
|
pub leading: LayoutAnchorX,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime trailing layout constraint.
|
|
|
|
pub trailing: LayoutAnchorX,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime bottom layout constraint.
|
|
|
|
pub bottom: LayoutAnchorY,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime width layout constraint.
|
|
|
|
pub width: LayoutAnchorDimension,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime height layout constraint.
|
|
|
|
pub height: LayoutAnchorDimension,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime center X layout constraint.
|
|
|
|
pub center_x: LayoutAnchorX,
|
|
|
|
|
|
|
|
/// A pointer to the Objective-C runtime center Y layout constraint.
|
|
|
|
pub center_y: LayoutAnchorY
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for View {
|
|
|
|
fn default() -> Self {
|
|
|
|
View::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl View {
|
|
|
|
/// Returns a default `View`, suitable for
|
|
|
|
pub fn new() -> Self {
|
|
|
|
let view: id = unsafe {
|
|
|
|
let view: id = msg_send![register_view_class(), new];
|
|
|
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
2020-03-20 14:02:46 -07:00
|
|
|
let _: () = msg_send![view, setWantsLayer:YES];
|
2020-03-19 20:07:44 -07:00
|
|
|
view
|
|
|
|
};
|
|
|
|
|
|
|
|
View {
|
|
|
|
internal_callback_ptr: None,
|
|
|
|
delegate: None,
|
|
|
|
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
|
|
|
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
|
|
|
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
|
|
|
|
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
|
|
|
|
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
|
|
|
|
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
|
|
|
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
|
|
|
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
2020-03-20 14:02:46 -07:00
|
|
|
objc: unsafe { ShareId::from_ptr(view) },
|
2020-03-19 20:07:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> View<T> where T: ViewDelegate + 'static {
|
|
|
|
/// Initializes a new View with a given `ViewDelegate`. This enables you to respond to events
|
|
|
|
/// and customize the view as a module, similar to class-based systems.
|
|
|
|
pub fn with(delegate: T) -> View<T> {
|
|
|
|
let delegate = Rc::new(RefCell::new(delegate));
|
|
|
|
|
|
|
|
let internal_callback_ptr = {
|
|
|
|
let cloned = Rc::clone(&delegate);
|
|
|
|
Rc::into_raw(cloned)
|
|
|
|
};
|
|
|
|
|
|
|
|
let view = unsafe {
|
|
|
|
let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
|
|
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
|
|
(&mut *view).set_ivar(VIEW_DELEGATE_PTR, internal_callback_ptr as usize);
|
|
|
|
view
|
|
|
|
};
|
|
|
|
|
2020-03-20 14:02:46 -07:00
|
|
|
let mut view = View {
|
2020-03-19 20:07:44 -07:00
|
|
|
internal_callback_ptr: Some(internal_callback_ptr),
|
2020-03-20 14:02:46 -07:00
|
|
|
delegate: None,
|
2020-03-19 20:07:44 -07:00
|
|
|
top: LayoutAnchorY::new(unsafe { msg_send![view, topAnchor] }),
|
|
|
|
leading: LayoutAnchorX::new(unsafe { msg_send![view, leadingAnchor] }),
|
|
|
|
trailing: LayoutAnchorX::new(unsafe { msg_send![view, trailingAnchor] }),
|
|
|
|
bottom: LayoutAnchorY::new(unsafe { msg_send![view, bottomAnchor] }),
|
|
|
|
width: LayoutAnchorDimension::new(unsafe { msg_send![view, widthAnchor] }),
|
|
|
|
height: LayoutAnchorDimension::new(unsafe { msg_send![view, heightAnchor] }),
|
|
|
|
center_x: LayoutAnchorX::new(unsafe { msg_send![view, centerXAnchor] }),
|
|
|
|
center_y: LayoutAnchorY::new(unsafe { msg_send![view, centerYAnchor] }),
|
2020-03-20 14:02:46 -07:00
|
|
|
objc: unsafe { ShareId::from_ptr(view) },
|
2020-03-19 20:07:44 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
let mut delegate = delegate.borrow_mut();
|
2020-03-20 15:03:19 -07:00
|
|
|
(*delegate).did_load(view.clone_as_handle());
|
2020-03-19 20:07:44 -07:00
|
|
|
}
|
|
|
|
|
2020-03-20 14:02:46 -07:00
|
|
|
view.delegate = Some(delegate);
|
2020-03-19 20:07:44 -07:00
|
|
|
view
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 14:02:46 -07:00
|
|
|
impl<T> View<T> {
|
2020-03-20 15:03:19 -07:00
|
|
|
/// 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
|
|
|
|
/// reference, customize and use the view but without the trickery of holding pieces of the
|
|
|
|
/// delegate - the `View` is the only true holder of those.
|
|
|
|
pub(crate) fn clone_as_handle(&self) -> View {
|
|
|
|
View {
|
|
|
|
internal_callback_ptr: None,
|
|
|
|
delegate: None,
|
|
|
|
top: self.top.clone(),
|
|
|
|
leading: self.leading.clone(),
|
|
|
|
trailing: self.trailing.clone(),
|
|
|
|
bottom: self.bottom.clone(),
|
|
|
|
width: self.width.clone(),
|
|
|
|
height: self.height.clone(),
|
|
|
|
center_x: self.center_x.clone(),
|
|
|
|
center_y: self.center_y.clone(),
|
|
|
|
objc: self.objc.clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-20 14:02:46 -07:00
|
|
|
/// Call this to set the background color for the backing layer.
|
|
|
|
pub fn set_background_color(&self, color: Color) {
|
|
|
|
let bg = color.into_platform_specific_color();
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
let cg: id = msg_send![bg, CGColor];
|
|
|
|
let layer: id = msg_send![&*self.objc, layer];
|
|
|
|
let _: () = msg_send![layer, setBackgroundColor:cg];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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.clone().into();
|
|
|
|
x.into_inner()
|
|
|
|
}).collect::<Vec<id>>().into();
|
|
|
|
|
|
|
|
let _: () = msg_send![&*self.objc, registerForDraggedTypes:types.into_inner()];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Layout for View<T> {
|
2020-03-19 20:07:44 -07:00
|
|
|
fn get_backing_node(&self) -> ShareId<Object> {
|
|
|
|
self.objc.clone()
|
|
|
|
}
|
|
|
|
|
2020-03-20 14:02:46 -07:00
|
|
|
fn add_subview<V: Layout>(&self, view: &V) {
|
|
|
|
let backing_node = view.get_backing_node();
|
|
|
|
|
|
|
|
unsafe {
|
|
|
|
let _: () = msg_send![&*self.objc, addSubview:backing_node];
|
|
|
|
}
|
|
|
|
}
|
2020-03-19 20:07:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> Drop for View<T> {
|
|
|
|
/// A bit of extra cleanup for delegate callback pointers.
|
|
|
|
fn drop(&mut self) {
|
2020-03-20 15:03:19 -07:00
|
|
|
if let Some(ptr) = &self.internal_callback_ptr {
|
2020-03-19 20:07:44 -07:00
|
|
|
unsafe {
|
|
|
|
let _ = Rc::from_raw(ptr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|