fix tooltips staying after parent is gone

This commit is contained in:
galister 2026-07-13 22:03:26 +09:00
parent acaabead52
commit c74c51f7cd
12 changed files with 88 additions and 25 deletions

View File

@ -206,7 +206,10 @@ impl<S: 'static> GuiPanel<S> {
setup_custom_label::<S>(&mut self.layout, &self.parser_state, elem, app); setup_custom_label::<S>(&mut self.layout, &self.parser_state, elem, app);
} else if let Ok(button) = self } else if let Ok(button) = self
.parser_state .parser_state
.fetch_component_from_widget_id_as::<ComponentButton>(&self.layout.state, elem.widget_id) .fetch_component_from_widget_id_as::<ComponentButton>(
&self.layout.state,
elem.widget_id,
)
{ {
setup_custom_button::<S>( setup_custom_button::<S>(
&mut self.layout, &mut self.layout,

View File

@ -372,8 +372,8 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
let anim_mult = app.wgui_theme.animation_mult; let anim_mult = app.wgui_theme.animation_mult;
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| { let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) = let Ok(button) = parser
parser.fetch_component_from_widget_id_as::<ComponentButton>(&layout.state, attribs.widget_id) .fetch_component_from_widget_id_as::<ComponentButton>(&layout.state, attribs.widget_id)
else { else {
return; return;
}; };

View File

@ -341,7 +341,7 @@ fn find_children<S>(
.layout .layout
.collect_children_ids_recursive(widget_id, &mut children); .collect_children_ids_recursive(widget_id, &mut children);
for (child, _) in children { for child in children {
if let Some(widget) = panel.layout.state.widgets.get_as::<WidgetSprite>(child) { if let Some(widget) = panel.layout.state.widgets.get_as::<WidgetSprite>(child) {
sprites.push(ChildWidget { sprites.push(ChildWidget {
id: child, id: child,

View File

@ -204,9 +204,10 @@ impl WvrWindowBackend {
let on_custom_attrib: OnCustomAttribFunc = let on_custom_attrib: OnCustomAttribFunc =
Box::new(move |layout, parser, attribs, _app| { Box::new(move |layout, parser, attribs, _app| {
let Ok(button) = let Ok(button) = parser.fetch_component_from_widget_id_as::<ComponentButton>(
parser.fetch_component_from_widget_id_as::<ComponentButton>(&layout.state, attribs.widget_id) &layout.state,
else { attribs.widget_id,
) else {
return; return;
}; };

View File

@ -95,8 +95,8 @@ pub fn create_whisper(
let xml = "gui/whisper.xml"; let xml = "gui/whisper.xml";
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| { let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) = let Ok(button) = parser
parser.fetch_component_from_widget_id_as::<ComponentButton>(&layout.state, attribs.widget_id) .fetch_component_from_widget_id_as::<ComponentButton>(&layout.state, attribs.widget_id)
else { else {
return; return;
}; };

View File

@ -143,7 +143,7 @@ impl ComponentTrait for ComponentButton {
.layout .layout
.collect_children_ids_recursive(self.data.id_rect, &mut children); .collect_children_ids_recursive(self.data.id_rect, &mut children);
for (child, _) in children { for child in children {
if let Some(mut widget) = data.layout.state.widgets.get_as::<WidgetSprite>(child) { if let Some(mut widget) = data.layout.state.widgets.get_as::<WidgetSprite>(child) {
if !state.id_sprite.is_null() && state.id_sprite != child { if !state.id_sprite.is_null() && state.id_sprite != child {
log::error!("Button with more than one sprite!"); log::error!("Button with more than one sprite!");
@ -184,6 +184,13 @@ impl ComponentTrait for ComponentButton {
} }
} }
} }
fn destroy(&self, data: &mut components::DestroyData) {
if let Some(comp) = self.state.borrow_mut().active_tooltip.take() {
comp.destroy(data);
data.destroy_widgets.push(comp.base().id);
}
}
} }
fn get_color2(color: &WguiColor, gradient_intensity: f32) -> WguiColor { fn get_color2(color: &WguiColor, gradient_intensity: f32) -> WguiColor {

View File

@ -11,7 +11,7 @@ use crate::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
color::{WguiColor, WguiColorName}, color::{WguiColor, WguiColorName},
components::{ components::{
Component, ComponentBase, ComponentTrait, RefreshData, Component, ComponentBase, ComponentTrait, DestroyData, RefreshData,
radio_group::ComponentRadioGroup, radio_group::ComponentRadioGroup,
tooltip::{self, ComponentTooltip, TooltipTrait}, tooltip::{self, ComponentTooltip, TooltipTrait},
}, },
@ -111,6 +111,13 @@ impl ComponentTrait for ComponentCheckbox {
fn refresh(&self, _data: &mut RefreshData) { fn refresh(&self, _data: &mut RefreshData) {
// nothing to do // nothing to do
} }
fn destroy(&self, data: &mut DestroyData) {
if let Some(comp) = self.state.borrow_mut().active_tooltip.take() {
comp.destroy(data);
data.destroy_widgets.push(comp.base().id);
}
}
} }
fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool) { fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool, hovered: bool) {

View File

@ -25,6 +25,11 @@ pub struct RefreshData<'a> {
pub layout: &'a mut Layout, pub layout: &'a mut Layout,
} }
pub struct DestroyData<'a> {
pub layout: &'a mut Layout,
pub destroy_widgets: &'a mut Vec<WidgetID>,
}
pub struct FocusChangeData<'a> { pub struct FocusChangeData<'a> {
pub common: &'a mut CallbackDataCommon<'a>, pub common: &'a mut CallbackDataCommon<'a>,
pub focused: bool, pub focused: bool,
@ -49,6 +54,7 @@ pub trait ComponentTrait: AnyTrait {
fn base_mut(&mut self) -> &mut ComponentBase; fn base_mut(&mut self) -> &mut ComponentBase;
fn refresh(&self, data: &mut RefreshData); fn refresh(&self, data: &mut RefreshData);
fn on_focus_change(&self, _data: &mut FocusChangeData) {} fn on_focus_change(&self, _data: &mut FocusChangeData) {}
fn destroy(&self, _data: &mut DestroyData) {}
} }
#[derive(Clone)] #[derive(Clone)]

View File

@ -7,7 +7,7 @@ use crate::{
animation::{Animation, AnimationCallback, AnimationEasing}, animation::{Animation, AnimationCallback, AnimationEasing},
color::{WguiColor, WguiColorName}, color::{WguiColor, WguiColorName},
components::{ components::{
Component, ComponentBase, ComponentTrait, RefreshData, Component, ComponentBase, ComponentTrait, DestroyData, RefreshData,
tooltip::{self, ComponentTooltip, TooltipTrait}, tooltip::{self, ComponentTooltip, TooltipTrait},
}, },
event::{ event::{
@ -160,6 +160,13 @@ impl ComponentTrait for ComponentSlider {
fn base_mut(&mut self) -> &mut ComponentBase { fn base_mut(&mut self) -> &mut ComponentBase {
&mut self.base &mut self.base
} }
fn destroy(&self, data: &mut DestroyData) {
if let Some(comp) = self.state.borrow_mut().active_tooltip.take() {
comp.destroy(data);
data.destroy_widgets.push(comp.base().id);
}
}
} }
impl ComponentSlider { impl ComponentSlider {

View File

@ -2,7 +2,7 @@ use crate::{
assets::AssetPathRc, assets::AssetPathRc,
color::WguiColor, color::WguiColor,
components::{ components::{
Component, ComponentBase, ComponentTrait, RefreshData, Component, ComponentBase, ComponentTrait, DestroyData, RefreshData,
button::{self, ComponentButton}, button::{self, ComponentButton},
}, },
event::CallbackDataCommon, event::CallbackDataCommon,
@ -71,6 +71,13 @@ impl ComponentTrait for ComponentTabs {
fn refresh(&self, _data: &mut RefreshData) { fn refresh(&self, _data: &mut RefreshData) {
// nothing to do // nothing to do
} }
fn destroy(&self, data: &mut DestroyData) {
for e in self.state.borrow_mut().mounted_entries.drain(..) {
e.button.destroy(data);
data.destroy_widgets.push(e.button.base().id);
}
}
} }
impl State { impl State {

View File

@ -7,7 +7,7 @@ use std::{
use crate::{ use crate::{
animation::Animations, animation::Animations,
components::{self, Component, ComponentWeak, FocusChangeData, RefreshData}, components::{self, Component, ComponentWeak, DestroyData, FocusChangeData, RefreshData},
drawing::{ drawing::{
self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack, self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack,
}, },
@ -283,21 +283,35 @@ impl Layout {
}) })
} }
pub fn collect_children_ids_recursive(&self, widget_id: WidgetID, out: &mut Vec<(WidgetID, taffy::NodeId)>) { pub fn collect_children_ids_recursive(&self, widget_id: WidgetID, out: &mut Vec<WidgetID>) {
let Some(node_id) = self.state.nodes.get(widget_id) else { let Some(node_id) = self.state.nodes.get(widget_id) else {
return; return;
}; };
for child_id in self.state.tree.child_ids(*node_id) { for child_id in self.state.tree.child_ids(*node_id) {
let child_widget_id = self.state.tree.get_node_context(child_id).unwrap(); let child_widget_id = self.state.tree.get_node_context(child_id).unwrap();
out.push((*child_widget_id, child_id)); out.push(*child_widget_id);
self.collect_children_ids_recursive(*child_widget_id, out); self.collect_children_ids_recursive(*child_widget_id, out);
} }
} }
fn remove_widget_single(&mut self, widget_id: WidgetID, node_id: Option<taffy::NodeId>) { fn remove_widget_single(&mut self, widget_id: WidgetID) {
self.state.widgets.remove_single(widget_id); self.state.widgets.remove_single(widget_id);
self.state.nodes.remove(widget_id); let node_id = self.state.nodes.remove(widget_id);
if let Some(component_weak) = self.state.components_by_widget_id.remove(widget_id)
&& let Some(component) = component_weak.upgrade()
{
let mut destroy_widgets = vec![];
component.destroy(&mut DestroyData {
layout: self,
destroy_widgets: &mut destroy_widgets,
});
for wid in destroy_widgets {
self.remove_widget_single(wid);
}
}
if let Some(node_id) = node_id { if let Some(node_id) = node_id {
self.registered_components_to_refresh.remove(&node_id); self.registered_components_to_refresh.remove(&node_id);
let _ = self.state.tree.remove(node_id); let _ = self.state.tree.remove(node_id);
@ -313,16 +327,15 @@ impl Layout {
self.mark_redraw(); self.mark_redraw();
} }
for (widget_id, node_id) in ids { for widget_id in ids {
self.remove_widget_single(widget_id, Some(node_id)); self.remove_widget_single(widget_id);
} }
} }
// remove widget and its children, recursively // remove widget and its children, recursively
pub fn remove_widget(&mut self, widget_id: WidgetID) { pub fn remove_widget(&mut self, widget_id: WidgetID) {
self.remove_children(widget_id); self.remove_children(widget_id);
let node_id = self.state.nodes.get(widget_id); self.remove_widget_single(widget_id);
self.remove_widget_single(widget_id, node_id.copied());
self.mark_redraw(); self.mark_redraw();
} }

View File

@ -97,7 +97,11 @@ pub trait Fetchable {
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>>; fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>>;
/// Fetch a component by widget ID and downcast it to a concrete component type `T` (see `components/mod.rs`) /// Fetch a component by widget ID and downcast it to a concrete component type `T` (see `components/mod.rs`)
fn fetch_component_from_widget_id_as<T: 'static>(&self, state: &LayoutState, widget_id: WidgetID) -> anyhow::Result<Rc<T>>; fn fetch_component_from_widget_id_as<T: 'static>(
&self,
state: &LayoutState,
widget_id: WidgetID,
) -> anyhow::Result<Rc<T>>;
/// Return a widget by its string ID /// Return a widget by its string ID
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID>; fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID>;
@ -168,7 +172,11 @@ impl Fetchable for ParserData {
state.fetch_component_by_widget_id(widget_id) state.fetch_component_by_widget_id(widget_id)
} }
fn fetch_component_from_widget_id_as<T: 'static>(&self, state: &LayoutState, widget_id: WidgetID) -> anyhow::Result<Rc<T>> { fn fetch_component_from_widget_id_as<T: 'static>(
&self,
state: &LayoutState,
widget_id: WidgetID,
) -> anyhow::Result<Rc<T>> {
state.fetch_component_from_widget_id_as(widget_id) state.fetch_component_from_widget_id_as(widget_id)
} }
@ -381,7 +389,11 @@ impl Fetchable for ParserState {
self.data.fetch_component_by_widget_id(state, widget_id) self.data.fetch_component_by_widget_id(state, widget_id)
} }
fn fetch_component_from_widget_id_as<T: 'static>(&self, state: &LayoutState, widget_id: WidgetID) -> anyhow::Result<Rc<T>> { fn fetch_component_from_widget_id_as<T: 'static>(
&self,
state: &LayoutState,
widget_id: WidgetID,
) -> anyhow::Result<Rc<T>> {
self.data.fetch_component_from_widget_id_as(state, widget_id) self.data.fetch_component_from_widget_id_as(state, widget_id)
} }