use std::{collections::HashMap, rc::Rc};
use incular_navigation::{Navigator, RouteId};
use incular_widgets::internal::{ElementId, WidgetTree};
use super::frame::Runtime;
pub type RouteFocusOwner = Rc<dyn Fn(&WidgetTree, ElementId) -> Option<RouteId>>;
pub struct RouteFocusState {
saved: HashMap<RouteId, Option<ElementId>>,
owner_of: RouteFocusOwner,
}
impl RouteFocusState {
pub fn new(owner_of: impl Fn(&WidgetTree, ElementId) -> Option<RouteId> + 'static) -> Self {
Self {
saved: HashMap::new(),
owner_of: Rc::new(owner_of),
}
}
pub fn save_focused(&mut self, runtime: &Runtime, route: RouteId) {
let focused = self.read_owned_focus(runtime, route);
self.saved.insert(route, focused);
}
pub(crate) fn read_owned_focus(&self, runtime: &Runtime, route: RouteId) -> Option<ElementId> {
runtime.focused_element().filter(|id| {
runtime.tree().element_exists(*id)
&& (self.owner_of)(runtime.tree(), *id) == Some(route)
})
}
pub(crate) fn commit_saved(&mut self, route: RouteId, saved: Option<ElementId>) {
self.saved.insert(route, saved);
}
pub fn restore_saved(&mut self, runtime: &mut Runtime, navigator: &Navigator, route: RouteId) {
if navigator.lifetime_of(route).is_none() {
return;
}
if let Some(saved) = self.saved.get(&route).copied().flatten()
&& Self::eligible(runtime, &self.owner_of, route, saved)
{
if runtime.focused_element() != Some(saved) {
runtime.set_focus(Some(saved));
}
return;
}
let current = runtime.focused_element();
let vacant =
current.is_none_or(|id| Self::orphaned(runtime, navigator, &self.owner_of, id));
if vacant
&& let Some(auto) = runtime.tree().autofocus_element()
&& (self.owner_of)(runtime.tree(), auto) == Some(route)
{
runtime.set_focus(Some(auto));
return;
}
if let Some(current) = current
&& Self::orphaned(runtime, navigator, &self.owner_of, current)
{
runtime.set_focus(None);
}
}
fn eligible(
runtime: &Runtime,
owner_of: &RouteFocusOwner,
route: RouteId,
id: ElementId,
) -> bool {
runtime.tree().element_exists(id)
&& runtime.tree().focusable_elements().contains(&id)
&& owner_of(runtime.tree(), id) == Some(route)
}
fn orphaned(
runtime: &Runtime,
navigator: &Navigator,
owner_of: &RouteFocusOwner,
id: ElementId,
) -> bool {
!runtime.tree().element_exists(id)
|| !runtime.tree().focusable_elements().contains(&id)
|| owner_of(runtime.tree(), id)
.is_some_and(|owner| navigator.lifetime_of(owner).is_none())
}
pub(crate) fn restore_if_vacant(
&mut self,
runtime: &mut Runtime,
navigator: &Navigator,
route: RouteId,
) {
if !matches!(self.saved.get(&route), Some(Some(_))) {
return;
}
let vacant = runtime
.focused_element()
.is_none_or(|current| Self::orphaned(runtime, navigator, &self.owner_of, current));
if vacant {
self.restore_saved(runtime, navigator, route);
}
}
pub fn forget(&mut self, route: RouteId) {
self.saved.remove(&route);
}
pub fn retain_mounted(&mut self, navigator: &Navigator) {
self.saved
.retain(|route, _| navigator.lifetime_of(*route).is_some());
}
}