use crate::app_state::with_app_state;
use crate::reactive::focus::{focus_path, release_focus, request_focus_deferred};
use crate::reactive::{RwSignal, Signal, create_signal};
use crate::tree::{Tree, WidgetId};
use crate::widgets::Rect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Attachment {
To(WidgetId),
Unattached,
Gone,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct WidgetRef {
signal: RwSignal<Rect>,
widget: RwSignal<Option<WidgetId>>,
}
impl WidgetRef {
pub fn rect(&self) -> Signal<Rect> {
self.signal.read_only()
}
pub fn widget(&self) -> Option<WidgetId> {
self.widget.try_get_untracked().flatten()
}
pub(crate) fn attachment(&self) -> Attachment {
match self.widget.try_get_untracked() {
None => Attachment::Gone,
Some(None) => Attachment::Unattached,
Some(Some(id)) => Attachment::To(id),
}
}
pub(crate) fn is_alive(&self) -> bool {
!matches!(self.attachment(), Attachment::Gone)
}
pub fn focus(&self) {
request_focus_deferred(*self);
}
pub fn blur(&self) {
if let Some(id) = self.widget() {
release_focus(id);
}
}
pub fn is_focused(&self) -> bool {
match self.widget() {
Some(id) => focus_path().widget() == Some(id),
None => false,
}
}
}
pub fn create_widget_ref() -> WidgetRef {
WidgetRef {
signal: create_signal(Rect::default()),
widget: create_signal(None),
}
}
pub(crate) fn register_widget_ref(id: WidgetId, widget_ref: WidgetRef) {
let Some(attached) = widget_ref.widget.try_get_untracked() else {
return;
};
if attached != Some(id) {
widget_ref.widget.set(Some(id));
}
with_app_state(|app| app.widget_refs.borrow_mut().insert(id, widget_ref));
}
pub(crate) fn update_widget_refs(tree: &Tree) {
crate::reactive::diagnostics::snapshot_zone(|| {
with_app_state(|app| {
app.widget_refs.borrow_mut().retain(|&id, widget_ref| {
if !widget_ref.is_alive() {
return false;
}
let attached = widget_ref.widget();
if let Some(rect) = tree.get_surface_relative_bounds(id) {
widget_ref.signal.set(rect);
true
} else {
if attached == Some(id) {
widget_ref.widget.set(None);
crate::reactive::focus::drop_pending_focus_for(*widget_ref);
}
false
}
});
})
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::owner::{create_root_owner, dispose_owner_now, with_owner};
use crate::widgets::container;
#[test]
fn a_dead_ref_answers_instead_of_panicking() {
create_root_owner();
let (widget_ref, scope) = with_owner(create_widget_ref);
dispose_owner_now(scope);
assert_eq!(widget_ref.widget(), None, "it points at no live widget");
assert!(!widget_ref.is_focused(), "and it does not hold the focus");
assert!(!widget_ref.is_alive());
}
#[test]
fn a_ref_whose_scope_died_leaves_the_registry_instead_of_panicking() {
create_root_owner();
let mut popup_tree = Tree::new();
let id = popup_tree.register(Box::new(container()));
let ((), popup_scope) = with_owner(|| {
register_widget_ref(id, create_widget_ref());
});
dispose_owner_now(popup_scope);
update_widget_refs(&Tree::new());
assert!(
with_app_state(|app| app.widget_refs.borrow().is_empty()),
"the dead ref is evicted, not carried into the next frame"
);
}
}