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.
This commit is contained in:
Jonathan Johnson 2024-03-06 09:50:29 -08:00
parent 0e02a513bb
commit 9c4ae939e1
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
3 changed files with 64 additions and 29 deletions

View file

@ -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

View file

@ -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<WidgetId>,
active: Option<WidgetId>,
unmounting: bool,
unmount_queue: Vec<WidgetId>,
}
impl PendingState<'_> {

View file

@ -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<WidgetId>,
) {
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<WidgetId>,
) {
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<MountedWidget> {
@ -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;
}
}
}