- 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.
243 lines
8.9 KiB
Rust
243 lines
8.9 KiB
Rust
//! Wraps `NSView` and `UIView` across platforms.
|
|
//!
|
|
//! This implementation errs towards the `UIView` side of things, and mostly acts as a wrapper to
|
|
//! bring `NSView` to the modern era. It does this by flipping the coordinate system to be what
|
|
//! people expect in 2020, and layer-backing all views by default.
|
|
//!
|
|
//! Views 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::View;
|
|
//! use cacao::window::{Window, WindowDelegate};
|
|
//!
|
|
//! #[derive(Default)]
|
|
//! struct AppWindow {
|
|
//! content: View,
|
|
//! red: View,
|
|
//! window: Window
|
|
//! }
|
|
//!
|
|
//! impl WindowDelegate for AppWindow {
|
|
//! fn did_load(&mut self, window: Window) {
|
|
//! window.set_minimum_content_size(300., 300.);
|
|
//! self.window = window;
|
|
//!
|
|
//! self.red.set_background_color(rgb(224, 82, 99));
|
|
//! 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.
|
|
|
|
use objc_id::ShareId;
|
|
use objc::runtime::{Class, Object};
|
|
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::pasteboard::PasteboardType;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
mod macos;
|
|
|
|
#[cfg(target_os = "macos")]
|
|
use macos::{register_scrollview_class, register_scrollview_class_with_delegate};
|
|
|
|
#[cfg(target_os = "ios")]
|
|
mod ios;
|
|
|
|
#[cfg(target_os = "ios")]
|
|
use ios::{register_view_class, register_view_class_with_delegate};
|
|
|
|
mod traits;
|
|
pub use traits::ScrollViewDelegate;
|
|
|
|
pub(crate) static SCROLLVIEW_DELEGATE_PTR: &str = "rstScrollViewDelegatePtr";
|
|
|
|
/// A helper method for instantiating view classes and applying default settings to them.
|
|
fn allocate_view(registration_fn: fn() -> *const Class) -> id {
|
|
unsafe {
|
|
let view: id = msg_send![registration_fn(), new];
|
|
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
let _: () = msg_send![view, setDrawsBackground:NO];
|
|
let _: () = msg_send![view, setWantsLayer:YES];
|
|
let _: () = msg_send![view, setBorderType:0];
|
|
let _: () = msg_send![view, setHorizontalScrollElasticity:1];
|
|
let _: () = msg_send![view, setHasVerticalScroller:YES];
|
|
}
|
|
|
|
view
|
|
}
|
|
}
|
|
|
|
/// A clone-able handler to a `NS/UIScrollView` reference in the Objective C runtime.
|
|
#[derive(Debug)]
|
|
pub struct ScrollView<T = ()> {
|
|
/// A pointer to the Objective-C runtime view controller.
|
|
pub objc: ShareId<Object>,
|
|
|
|
/// A pointer to the delegate for this view.
|
|
pub delegate: Option<Box<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 ScrollView {
|
|
fn default() -> Self {
|
|
ScrollView::new()
|
|
}
|
|
}
|
|
|
|
impl ScrollView {
|
|
/// Returns a default `View`, suitable for
|
|
pub fn new() -> Self {
|
|
let view = allocate_view(register_scrollview_class);
|
|
|
|
ScrollView {
|
|
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] }),
|
|
objc: unsafe { ShareId::from_ptr(view) },
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> ScrollView<T> where T: ScrollViewDelegate + '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) -> ScrollView<T> {
|
|
let mut delegate = Box::new(delegate);
|
|
|
|
let view = allocate_view(register_scrollview_class_with_delegate::<T>);
|
|
unsafe {
|
|
let ptr: *const T = &*delegate;
|
|
(&mut *view).set_ivar(SCROLLVIEW_DELEGATE_PTR, ptr as usize);
|
|
};
|
|
|
|
let mut view = ScrollView {
|
|
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] }),
|
|
objc: unsafe { ShareId::from_ptr(view) },
|
|
};
|
|
|
|
(&mut delegate).did_load(view.clone_as_handle());
|
|
view.delegate = Some(delegate);
|
|
view
|
|
}
|
|
}
|
|
|
|
impl<T> ScrollView<T> {
|
|
/// 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) -> ScrollView {
|
|
ScrollView {
|
|
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()
|
|
}
|
|
}
|
|
|
|
/// Call this to set the background color for the backing layer.
|
|
pub fn set_background_color<C: AsRef<Color>>(&self, color: C) {
|
|
// @TODO: This is wrong.
|
|
let color = color.as_ref().cg_color();
|
|
|
|
unsafe {
|
|
let layer: id = msg_send![&*self.objc, layer];
|
|
let _: () = msg_send![layer, setBackgroundColor:color];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Layout for ScrollView<T> {
|
|
fn get_backing_node(&self) -> ShareId<Object> {
|
|
self.objc.clone()
|
|
}
|
|
|
|
fn add_subview<V: Layout>(&self, view: &V) {
|
|
let backing_node = view.get_backing_node();
|
|
|
|
unsafe {
|
|
let _: () = msg_send![&*self.objc, addSubview:backing_node];
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> Drop for ScrollView<T> {
|
|
/// A bit of extra cleanup for delegate callback pointers. If the originating `View` is being
|
|
/// dropped, we do some logic to clean it all up (e.g, we go ahead and check to see if
|
|
/// 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.
|
|
fn drop(&mut self) {
|
|
if self.delegate.is_some() {
|
|
unsafe {
|
|
let superview: id = msg_send![&*self.objc, superview];
|
|
if superview != nil {
|
|
let _: () = msg_send![&*self.objc, removeFromSuperview];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|