Begin moving certain core methods to Layout.

- Drag and drop, hidden, and so on now live on on the `Layout` trait,
  which helps make the logic less of a hassle to support (these methods
  are almost always guaranteed to exist on any view type, and it's
  easier to just noop them on a specific view if need be).

- Begin reworking auto-drop-remove-from-superview logic by including an
  `is_handle` flag on `View`; will need to extend this work to others.
This commit is contained in:
Ryan McGrath 2021-03-26 17:51:37 -07:00
parent 46ee9e2ea8
commit 1b0be74fc8
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
12 changed files with 162 additions and 60 deletions

View file

@ -248,12 +248,20 @@ impl Layout for Button {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl Layout for &Button { impl Layout for &Button {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl Drop for Button { impl Drop for Button {

View file

@ -117,21 +117,16 @@ impl ImageView {
let _: () = msg_send![obj, setImage:&*image.0]; let _: () = msg_send![obj, setImage:&*image.0];
}); });
} }
pub fn set_hidden(&self, hidden: bool) {
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, setHidden:match hidden {
true => YES,
false => NO
}];
});
}
} }
impl Layout for ImageView { impl Layout for ImageView {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl Drop for ImageView { impl Drop for ImageView {

View file

@ -257,6 +257,10 @@ impl<T> Layout for TextField<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl<T> Drop for TextField<T> { impl<T> Drop for TextField<T> {

View file

@ -7,17 +7,34 @@ use objc::{msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::ShareId; use objc_id::ShareId;
use crate::foundation::{id, YES, NO}; use crate::foundation::{id, nil, to_bool, YES, NO, NSArray, NSString};
use crate::geometry::Rect; use crate::geometry::Rect;
use crate::pasteboard::PasteboardType;
/// A trait that view wrappers must conform to. Enables managing the subview tree. /// A trait that view wrappers must conform to. Enables managing the subview tree.
#[allow(unused_variables)] #[allow(unused_variables)]
pub trait Layout { pub trait Layout {
/// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep /// Used for mutably interacting with the underlying Objective-C instance.
/// the general lazy-loading approach Cocoa has. This may change in the future, and in general
/// this shouldn't affect your code too much (if at all).
fn with_backing_node<F: Fn(id)>(&self, handler: F); fn with_backing_node<F: Fn(id)>(&self, handler: F);
/// Used for checking backing properties of the underlying Objective-C instance, without
/// needing a mutable borrow.
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R;
/// Sets whether this needs to be redrawn before being displayed.
///
/// If you're updating data that dynamically impacts this view, mark this as true - the next
/// pass from the system will redraw it accordingly, and set the underlying value back to
/// `false`.
fn set_needs_display(&self, needs_display: bool) {
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, setNeedsDisplay:match needs_display {
true => YES,
false => NO
}];
});
}
/// Adds another Layout-backed control or view as a subview of this view. /// Adds another Layout-backed control or view as a subview of this view.
fn add_subview<V: Layout>(&self, view: &V) { fn add_subview<V: Layout>(&self, view: &V) {
self.with_backing_node(|backing_node| { self.with_backing_node(|backing_node| {
@ -60,4 +77,82 @@ pub trait Layout {
}]; }];
}); });
} }
/// Sets whether the view for this is hidden or not.
///
/// When hidden, widgets don't receive events and is not visible.
fn set_hidden(&self, hide: bool) {
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, setHidden:match hide {
true => YES,
false => NO
}];
});
}
/// Returns whether this is hidden or not.
///
/// Note that this can report `false` if an ancestor widget is hidden, thus hiding this - to check in
/// that case, you may want `is_hidden_or_ancestor_is_hidden()`.
fn is_hidden(&self) -> bool {
self.get_from_backing_node(|obj| {
to_bool(unsafe {
msg_send![obj, isHidden]
})
})
}
/// Returns whether this is hidden, *or* whether an ancestor view is hidden.
fn is_hidden_or_ancestor_is_hidden(&self) -> bool {
self.get_from_backing_node(|obj| {
to_bool(unsafe {
msg_send![obj, isHiddenOrHasHiddenAncestor]
})
})
}
/// Register this view for drag and drop operations.
fn register_for_dragged_types(&self, types: &[PasteboardType]) {
let types: NSArray = types.into_iter().map(|t| {
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
/// Unregisters this as a target for drag and drop operations.
fn unregister_dragged_types(&self) {
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, unregisterDraggedTypes];
});
}
/// Sets whether this posts notifications when the frame rectangle changes.
///
/// If you have a high performance tableview or collectionview that has issues, disabling these
/// can be helpful - but always test!
fn set_posts_frame_change_notifications(&self, posts: bool) {
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, setPostsFrameChangedNotifications:match posts {
true => YES,
false => NO
}];
});
}
/// Sets whether this posts notifications when the bounds rectangle changes.
///
/// If you have a high performance tableview or collectionview that has issues, disabling these
/// can be helpful - but always test!
fn set_posts_bounds_change_notifications(&self, posts: bool) {
self.with_backing_node(|obj| unsafe {
let _: () = msg_send![obj, setPostsBoundsChangedNotifications:match posts {
true => YES,
false => NO
}];
});
}
} }

View file

@ -51,7 +51,6 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger}; use crate::foundation::{id, nil, YES, NO, NSArray, NSString, NSInteger, NSUInteger};
use crate::color::Color; use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::scrollview::ScrollView; use crate::scrollview::ScrollView;
use crate::utils::{os, CellFactory, CGSize}; use crate::utils::{os, CellFactory, CGSize};
use crate::utils::properties::{ObjcProperty, PropertyNullable}; use crate::utils::properties::{ObjcProperty, PropertyNullable};
@ -571,20 +570,6 @@ impl<T> ListView<T> {
}); });
} }
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
/// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove /// Reloads the underlying ListView. This is more expensive than handling insert/reload/remove
/// calls yourself, but often easier to implement. /// calls yourself, but often easier to implement.
/// ///
@ -634,6 +619,16 @@ impl<T> Layout for ListView<T> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
self.scrollview.objc.with_mut(handler); self.scrollview.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
// On macOS, we need to provide the scrollview for layout purposes - iOS and tvOS will know
// what to do normally.
//
// @TODO: Review this, as property access isn't really used in the same place as layout
// stuff... hmm...
#[cfg(target_os = "macos")]
self.scrollview.objc.get(handler)
}
} }
impl<T> Drop for ListView<T> { impl<T> Drop for ListView<T> {

View file

@ -52,7 +52,6 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString};
use crate::color::Color; use crate::color::Color;
use crate::layer::Layer; use crate::layer::Layer;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension};
use crate::pasteboard::PasteboardType;
use crate::view::ViewDelegate; use crate::view::ViewDelegate;
use crate::utils::properties::ObjcProperty; use crate::utils::properties::ObjcProperty;
@ -294,26 +293,16 @@ impl<T> ListViewRow<T> {
(&mut *obj).set_ivar(BACKGROUND_COLOR, color); (&mut *obj).set_ivar(BACKGROUND_COLOR, color);
}); });
} }
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
let types: NSArray = types.into_iter().map(|t| {
// This clone probably doesn't need to be here, but it should also be cheap as
// this is just an enum... and this is not an oft called method.
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
} }
impl<T> Layout for ListViewRow<T> { impl<T> Layout for ListViewRow<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl<T> Drop for ListViewRow<T> { impl<T> Drop for ListViewRow<T> {

View file

@ -176,6 +176,10 @@ impl Layout for ProgressIndicator {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl Drop for ProgressIndicator { impl Drop for ProgressIndicator {

View file

@ -225,6 +225,10 @@ impl<T> Layout for ScrollView<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl<T> Drop for ScrollView<T> { impl<T> Drop for ScrollView<T> {

View file

@ -98,8 +98,10 @@ impl Switch {
/// Attaches a callback for button press events. Don't get too creative now... /// Attaches a callback for button press events. Don't get too creative now...
/// best just to message pass or something. /// best just to message pass or something.
pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) { pub fn set_action<F: Fn() + Send + Sync + 'static>(&mut self, action: F) {
//let handler = TargetActionHandler::new(&*self.objc, action); // @TODO: This probably isn't ideal but gets the job done for now; needs revisiting.
//self.handler = Some(handler); let this = self.objc.get(|obj| unsafe { ShareId::from_ptr(msg_send![obj, self]) });
let handler = TargetActionHandler::new(&*this, action);
self.handler = Some(handler);
} }
} }
@ -107,10 +109,14 @@ impl Layout for Switch {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
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 Switch. This is not allowed in Cacao. If you think this should be supported,
open a discussion on the GitHub repo. open a discussion on the GitHub repo.
"#); "#);
} }

View file

@ -345,6 +345,10 @@ impl<T> Layout for Label<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl<T> Drop for Label<T> { impl<T> Drop for Label<T> {

View file

@ -232,23 +232,16 @@ impl<T> View<T> {
}); });
} }
/// Register this view for drag and drop operations.
pub fn register_for_dragged_types(&self, types: &[PasteboardType]) {
let types: NSArray = types.into_iter().map(|t| {
let x: NSString = (*t).into();
x.into()
}).collect::<Vec<id>>().into();
self.objc.with_mut(|obj| unsafe {
let _: () = msg_send![obj, registerForDraggedTypes:&*types];
});
}
} }
impl<T> Layout for View<T> { impl<T> Layout for View<T> {
fn with_backing_node<F: Fn(id)>(&self, handler: F) { fn with_backing_node<F: Fn(id)>(&self, handler: F) {
self.objc.with_mut(handler); self.objc.with_mut(handler);
} }
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
self.objc.get(handler)
}
} }
impl<T> Drop for View<T> { impl<T> Drop for View<T> {

View file

@ -211,9 +211,14 @@ impl<T> WebView<T> {
} }
impl<T> Layout for WebView<T> { impl<T> Layout for WebView<T> {
/// Returns the Objective-C object used for handling the view heirarchy. fn with_backing_node<F: Fn(id)>(&self, handler: F) {
fn get_backing_node(&self) -> ShareId<Object> { // @TODO: Fix.
self.objc.clone() //self.objc.with_mut(handler);
}
fn get_from_backing_node<F: Fn(&Object) -> R, R>(&self, handler: F) -> R {
// @TODO: Fix.
//self.objc.get(handler)
} }
/// Currently, this is a noop. Theoretically there is reason to support this, but in practice /// Currently, this is a noop. Theoretically there is reason to support this, but in practice