From 1b0be74fc8876453ed6389ad5dece5f11d628e8b Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 26 Mar 2021 17:51:37 -0700 Subject: [PATCH] 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. --- src/button/mod.rs | 8 ++++ src/image/mod.rs | 13 ++--- src/input/mod.rs | 4 ++ src/layout/traits.rs | 103 ++++++++++++++++++++++++++++++++++++++-- src/listview/mod.rs | 25 ++++------ src/listview/row/mod.rs | 19 ++------ src/progress/mod.rs | 4 ++ src/scrollview/mod.rs | 4 ++ src/switch.rs | 12 +++-- src/text/label/mod.rs | 4 ++ src/view/mod.rs | 15 ++---- src/webview/mod.rs | 11 +++-- 12 files changed, 162 insertions(+), 60 deletions(-) diff --git a/src/button/mod.rs b/src/button/mod.rs index 3473ea6..770fefe 100644 --- a/src/button/mod.rs +++ b/src/button/mod.rs @@ -248,12 +248,20 @@ impl Layout for Button { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Layout for &Button { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for Button { diff --git a/src/image/mod.rs b/src/image/mod.rs index 1597733..313ad3f 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -117,21 +117,16 @@ impl ImageView { 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 { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for ImageView { diff --git a/src/input/mod.rs b/src/input/mod.rs index a5db174..f1230d7 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -257,6 +257,10 @@ impl Layout for TextField { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for TextField { diff --git a/src/layout/traits.rs b/src/layout/traits.rs index 260eebe..0805629 100644 --- a/src/layout/traits.rs +++ b/src/layout/traits.rs @@ -7,17 +7,34 @@ use objc::{msg_send, sel, sel_impl}; use objc::runtime::Object; 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::pasteboard::PasteboardType; /// A trait that view wrappers must conform to. Enables managing the subview tree. #[allow(unused_variables)] pub trait Layout { - /// Returns a reference to the backing Objective-C layer. This is optional, as we try to keep - /// 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). + /// Used for mutably interacting with the underlying Objective-C instance. fn with_backing_node(&self, handler: F); + /// Used for checking backing properties of the underlying Objective-C instance, without + /// needing a mutable borrow. + fn get_from_backing_node 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. fn add_subview(&self, view: &V) { 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::>().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 + }]; + }); + } } diff --git a/src/listview/mod.rs b/src/listview/mod.rs index 0653b74..12b21bf 100644 --- a/src/listview/mod.rs +++ b/src/listview/mod.rs @@ -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::color::Color; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; -use crate::pasteboard::PasteboardType; use crate::scrollview::ScrollView; use crate::utils::{os, CellFactory, CGSize}; use crate::utils::properties::{ObjcProperty, PropertyNullable}; @@ -571,20 +570,6 @@ impl ListView { }); } - /// 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::>().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 /// calls yourself, but often easier to implement. /// @@ -634,6 +619,16 @@ impl Layout for ListView { #[cfg(target_os = "macos")] self.scrollview.objc.with_mut(handler); } + + fn get_from_backing_node 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 Drop for ListView { diff --git a/src/listview/row/mod.rs b/src/listview/row/mod.rs index 0f0f0eb..935ed58 100644 --- a/src/listview/row/mod.rs +++ b/src/listview/row/mod.rs @@ -52,7 +52,6 @@ use crate::foundation::{id, nil, YES, NO, NSArray, NSString}; use crate::color::Color; use crate::layer::Layer; use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; -use crate::pasteboard::PasteboardType; use crate::view::ViewDelegate; use crate::utils::properties::ObjcProperty; @@ -294,26 +293,16 @@ impl ListViewRow { (&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::>().into(); - - self.objc.with_mut(|obj| unsafe { - let _: () = msg_send![obj, registerForDraggedTypes:&*types]; - }); - } } impl Layout for ListViewRow { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for ListViewRow { diff --git a/src/progress/mod.rs b/src/progress/mod.rs index 57a8638..260ac3f 100644 --- a/src/progress/mod.rs +++ b/src/progress/mod.rs @@ -176,6 +176,10 @@ impl Layout for ProgressIndicator { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for ProgressIndicator { diff --git a/src/scrollview/mod.rs b/src/scrollview/mod.rs index 0820159..535609e 100644 --- a/src/scrollview/mod.rs +++ b/src/scrollview/mod.rs @@ -225,6 +225,10 @@ impl Layout for ScrollView { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for ScrollView { diff --git a/src/switch.rs b/src/switch.rs index f3b5242..33c9681 100644 --- a/src/switch.rs +++ b/src/switch.rs @@ -98,8 +98,10 @@ impl Switch { /// Attaches a callback for button press events. Don't get too creative now... /// best just to message pass or something. pub fn set_action(&mut self, action: F) { - //let handler = TargetActionHandler::new(&*self.objc, action); - //self.handler = Some(handler); + // @TODO: This probably isn't ideal but gets the job done for now; needs revisiting. + 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(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } fn add_subview(&self, _view: &V) { 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. "#); } diff --git a/src/text/label/mod.rs b/src/text/label/mod.rs index 7c4d46f..87ffcb8 100644 --- a/src/text/label/mod.rs +++ b/src/text/label/mod.rs @@ -345,6 +345,10 @@ impl Layout for Label { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for Label { diff --git a/src/view/mod.rs b/src/view/mod.rs index 2812035..4153d24 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -232,23 +232,16 @@ impl View { }); } - /// 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::>().into(); - - self.objc.with_mut(|obj| unsafe { - let _: () = msg_send![obj, registerForDraggedTypes:&*types]; - }); - } } impl Layout for View { fn with_backing_node(&self, handler: F) { self.objc.with_mut(handler); } + + fn get_from_backing_node R, R>(&self, handler: F) -> R { + self.objc.get(handler) + } } impl Drop for View { diff --git a/src/webview/mod.rs b/src/webview/mod.rs index 632d0cc..ba6a492 100644 --- a/src/webview/mod.rs +++ b/src/webview/mod.rs @@ -211,9 +211,14 @@ impl WebView { } impl Layout for WebView { - /// Returns the Objective-C object used for handling the view heirarchy. - fn get_backing_node(&self) -> ShareId { - self.objc.clone() + fn with_backing_node(&self, handler: F) { + // @TODO: Fix. + //self.objc.with_mut(handler); + } + + fn get_from_backing_node 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