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