diff --git a/ide/CHANGELOG.md b/ide/CHANGELOG.md index 0d27f80f9e8c..027dbf2e8550 100644 --- a/ide/CHANGELOG.md +++ b/ide/CHANGELOG.md @@ -31,6 +31,9 @@ `Column` datatypes are properly visualized. Scatter Plot can display points of different color, shape and size, all as defined by the data within the `Table`. +- [`onHide callback for visualizations][1383]. JavaScript visualizations can now + implement a method `onHide()` that will be called whenever the visualization + is hidden or closed on screen.
![Bug Fixes](/docs/assets/tags/bug_fixes.svg) diff --git a/ide/docs/product/visualizations.md b/ide/docs/product/visualizations.md index 4080fe0f574c..3dd814f89791 100644 --- a/ide/docs/product/visualizations.md +++ b/ide/docs/product/visualizations.md @@ -275,6 +275,11 @@ In particular: from the server. Note that the visualization will receive the "full data" if you are not using the `setPreprocessor` method. +- ### [Optional] Function `onHide` + + The `onHide()` method is called whenever the visualization is hidden or closed + on screen. + - ### [Optional] Function `setSize` The `setSize(size)` method is called on every size change of the diff --git a/ide/src/rust/ensogl/lib/core/src/display/object/class.rs b/ide/src/rust/ensogl/lib/core/src/display/object/class.rs index c57931e8c6bb..b9c3ad7c1058 100644 --- a/ide/src/rust/ensogl/lib/core/src/display/object/class.rs +++ b/ide/src/rust/ensogl/lib/core/src/display/object/class.rs @@ -100,7 +100,7 @@ impl Debug for Callbacks { type NewParentDirty = dirty::SharedBool<()>; type ChildrenDirty = dirty::SharedSet; -type RemovedChildren = dirty::SharedVector,OnDirtyCallback>; +type RemovedChildren = dirty::SharedVector,OnDirtyCallback>; type TransformDirty = dirty::SharedBool; type SceneLayerDirty = dirty::SharedBool; @@ -250,9 +250,11 @@ impl Model { /// Removes child by a given index. Does nothing if the index was incorrect. fn remove_child_by_index(&self, index:usize) { self.children.borrow_mut().remove(index).for_each(|child| { - child.upgrade().for_each(|child| child.unsafe_unset_parent_without_update()); + if let Some(child) = child.upgrade() { + child.unsafe_unset_parent_without_update(); + self.dirty.removed_children.set(child); + } self.dirty.children.unset(&index); - self.dirty.removed_children.set(child); }); } @@ -355,16 +357,14 @@ impl Model { if self.dirty.removed_children.check_all() { debug!(self.logger, "Updating removed children.", || { for child in self.dirty.removed_children.take().into_iter() { - if let Some(child) = child.upgrade() { - if !child.has_visible_parent() { - child.set_vis_false(host); - } - // Even if the child is visible at this point, it does not mean that it - // should be visible after the entire update. Therefore, we must ensure that - // "removed children" lists in its subtree will be managed. - // See also test `visibility_test3`. - child.take_removed_children_and_update_their_visibility(host); + if !child.has_visible_parent() { + child.set_vis_false(host); } + // Even if the child is visible at this point, it does not mean that it + // should be visible after the entire update. Therefore, we must ensure that + // "removed children" lists in its subtree will be managed. + // See also test `visibility_test3`. + child.take_removed_children_and_update_their_visibility(host); } }) } @@ -505,7 +505,8 @@ impl Model { } /// Sets a callback which will be called with a reference to scene when the object will be - /// hidden (detached from display object graph). + /// hidden (detached from display object graph). This will also happen when the last `Instance` + /// referring to the object gets dropped. pub fn set_on_hide(&self, f:F) where F : Fn(&Host) + 'static{ self.callbacks.on_hide.set(Box::new(f)) @@ -573,6 +574,16 @@ pub struct Instance { rc : Rc> } +impl Drop for Instance { + fn drop(&mut self) { + // If this is the last reference, remove it from its parent. If there is a parent, then this + // will create a new reference which will be dropped on the parent's next update. + if Rc::strong_count(&self.rc) == 1 { + self.unset_parent(); + } + } +} + impl Deref for Instance { type Target = Rc>; fn deref(&self) -> &Self::Target { @@ -1449,4 +1460,23 @@ mod tests { assert_eq!(node5.is_visible(),false); assert_eq!(node6.is_visible(),false); } + + #[test] + fn hide_after_drop_test() { + let node1 = Instance::<()>::new(Logger::new("node1")); + let node2 = Instance::<()>::new(Logger::new("node2")); + node1.add_child(&node2); + + node1.force_set_visibility(true); + node1.update(&()); + assert!(node2.is_visible()); + + let node1_was_hidden = Rc::new(Cell::new(false)); + let node1_was_hidden_clone = node1_was_hidden.clone(); + node2.set_on_hide(move |_| node1_was_hidden_clone.set(true)); + + drop(node2); + node1.update(&()); + assert!(node1_was_hidden.get()); + } } diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs index 95a4be9a22d0..3ad5d6545db5 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/definition.rs @@ -41,6 +41,7 @@ pub mod field { pub mod method { pub const ON_DATA_RECEIVED : &str = "onDataReceived"; pub const SET_SIZE : &str = "setSize"; + pub const ON_HIDE : &str = "onHide"; } diff --git a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs index e19b78e87cd7..a81c3d73f2f6 100644 --- a/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs +++ b/ide/src/rust/ide/view/graph-editor/src/component/visualization/foreign/java_script/instance.rs @@ -89,6 +89,7 @@ type PreprocessorCallbackCell = Rc> #[allow(missing_docs)] pub struct InstanceModel { pub root_node : DomSymbol, + display_object : display::object::Instance, pub logger : Logger, on_data_received : Rc>, set_size : Rc>, @@ -119,7 +120,6 @@ impl InstanceModel { let bg_blue = bg_color.blue*255.0; let bg_hex = format!("rgba({},{},{},{})",bg_red,bg_green,bg_blue,bg_color.alpha); root_node.dom().set_style_or_warn("background",bg_hex,logger); - Ok(root_node) } @@ -169,8 +169,17 @@ impl InstanceModel { let set_size = Rc::new(set_size); let object = Rc::new(object); let scene = scene.clone_ref(); + let display_object = display::object::Instance::new(Logger::new("")); + display_object.add_child(root_node.display_object()); + let on_hide = get_method(&object.as_ref(), method::ON_HIDE).ok(); + if let Some(f) = on_hide { + display_object.set_on_hide(move |_| { + let context = &JsValue::NULL; + let _ = f.call0(context); + }); + } Ok(InstanceModel{object,on_data_received,set_size,root_node,logger,preprocessor_change, - scene}) + display_object,scene}) } /// Hooks the root node into the given scene. @@ -293,7 +302,7 @@ impl From for visualization::Instance { impl display::Object for Instance { fn display_object(&self) -> &display::object::Instance { - &self.model.root_node.display_object() + &self.model.display_object } }