From 9c4ae939e1f72ad4850f50d7ba69e0ea66f1b9ab Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Wed, 6 Mar 2024 09:50:29 -0800 Subject: [PATCH] Implemented nested widget unmounted events Closes #138 This implementation works around most of the locking issues that arose the first few times I tried fixing this. Unfortunately it's been just long enough for me to forget how I triggered some catastrophic issues in the past, but all of the current examples that would invoke this behavior continue to work, and some of the side projects that have some weird usages also still work. --- CHANGELOG.md | 2 ++ src/context.rs | 28 ++++++++++++++++++---- src/tree.rs | 63 +++++++++++++++++++++++++++++++------------------- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 697ad49..0867e3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 The rendering behavior remains unchanged, and the image will scale correctly within whatever bounds it is given. +- `Widget::unmounted` is now invoked for all widgets in the hierarchy. + Previously, only the parent widget was having its unmounted event invoked. ### Changed diff --git a/src/context.rs b/src/context.rs index 7281075..c0ce13a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -811,11 +811,25 @@ pub trait AsEventContext { /// Removes a widget from the hierarchy. fn remove_child(&mut self, child: &MountedWidget) { let mut context = self.as_event_context(); - child - .lock() - .as_widget() - .unmounted(&mut context.for_other(child)); - context.tree.remove_child(child, &context.current_node); + if context.pending_state.unmounting { + context.pending_state.unmount_queue.push(child.id()); + } else { + context.pending_state.unmounting = true; + context.pending_state.unmount_queue.push(child.id()); + while let Some(to_unmount) = context.pending_state.unmount_queue.pop() { + let Some(mut unmount_context) = context.for_other(&to_unmount) else { + continue; + }; + child.lock().as_widget().unmounted(&mut unmount_context); + unmount_context.widget.tree.remove_child( + child, + &unmount_context.widget.current_node, + &mut unmount_context.widget.pending_state.unmount_queue, + ); + } + + context.pending_state.unmounting = false; + } } } @@ -865,6 +879,8 @@ impl<'context> WidgetContext<'context> { .active_widget() .and_then(|id| tree.widget_from_node(id).map(|w| w.id())), focus_is_advancing: false, + unmount_queue: Vec::new(), + unmounting: false, }), tree, effective_styles: current_node.effective_styles(), @@ -1215,6 +1231,8 @@ struct PendingWidgetState { focus_is_advancing: bool, focus: Option, active: Option, + unmounting: bool, + unmount_queue: Vec, } impl PendingState<'_> { diff --git a/src/tree.rs b/src/tree.rs index 9423e7c..5f4aa78 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -66,9 +66,14 @@ impl Tree { } } - pub fn remove_child(&self, child: &MountedWidget, parent: &MountedWidget) { + pub fn remove_child( + &self, + child: &MountedWidget, + parent: &MountedWidget, + children_to_unmount: &mut Vec, + ) { let mut data = self.data.lock().ignore_poison(); - data.remove_child(child.node_id, parent.node_id); + data.remove_child(child.node_id, parent.node_id, children_to_unmount); if child.widget.is_default() { data.defaults.retain(|id| *id != child.node_id); @@ -479,32 +484,37 @@ impl TreeData { } } - fn remove_child(&mut self, child: LotId, parent: LotId) { - let removed_node = self.nodes.remove(child).expect("widget already removed"); + fn remove_child( + &mut self, + child: LotId, + parent: LotId, + children_to_unmount: &mut Vec, + ) { + let Some(removed_node) = self.nodes.remove(child) else { + return; + }; self.nodes_by_id.remove(&removed_node.widget.id()); - let parent = &mut self.nodes[parent]; - let index = parent - .children - .iter() - .enumerate() - .find_map(|(index, c)| (*c == child).then_some(index)) - .expect("child not found in parent"); - parent.children.remove(index); - let mut detached_nodes = removed_node.children; + if let Some(parent) = self.nodes.get_mut(parent) { + let index = parent + .children + .iter() + .enumerate() + .find_map(|(index, c)| (*c == child).then_some(index)) + .expect("child not found in parent"); + parent.children.remove(index); + } + + children_to_unmount.extend( + removed_node + .children + .into_iter() + .map(|id| self.nodes[id].widget.id()), + ); if let Some(next_focus) = removed_node.widget.next_focus() { self.previous_focuses.remove(&next_focus); } - - while let Some(node) = detached_nodes.pop() { - let mut node = self.nodes.remove(node).expect("detached node missing"); - self.nodes_by_id.remove(&node.widget.id()); - if let Some(next_focus) = node.widget.next_focus() { - self.previous_focuses.remove(&next_focus); - } - detached_nodes.append(&mut node.children); - } } pub(crate) fn widget_hierarchy(&self, mut widget: LotId, tree: &Tree) -> Vec { @@ -540,7 +550,9 @@ impl TreeData { } fn invalidate(&mut self, id: LotId, include_hierarchy: bool) { - let mut node = &mut self.nodes[id]; + let Some(mut node) = self.nodes.get_mut(id) else { + return; + }; loop { node.layout = None; node.last_layout_query = None; @@ -548,7 +560,10 @@ impl TreeData { let (true, Some(parent)) = (include_hierarchy, node.parent) else { break; }; - node = &mut self.nodes[parent]; + let Some(parent_node) = self.nodes.get_mut(parent) else { + break; + }; + node = parent_node; } } }