use crate::app::context::{FocusChanged, FocusChangedHook, FocusEntry, FocusPolicy};
use crate::app::input::focus::{self, FocusDirection};
use crate::callback::Callback;
use crate::core::element::Key;
use crate::core::node::{NodeId, NodeTree, OverlayRoot};
use crate::layout::tag::{Tag, tag_of_node};
use crate::overlay::OverlayId;
use crate::runtime::FocusRequest;
const MAX_FOCUS_STACK_DEPTH: usize = 32;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum OverlayKey {
Managed(OverlayId),
Node(NodeId),
}
impl OverlayKey {
pub(crate) fn of(overlay: &OverlayRoot) -> Self {
overlay
.overlay_id
.map_or(Self::Node(overlay.id), Self::Managed)
}
}
fn overlay_is_live(tree: &NodeTree, key: OverlayKey) -> bool {
tree.overlay_roots()
.iter()
.any(|overlay| OverlayKey::of(overlay) == key)
}
#[derive(Clone)]
pub(crate) struct FocusStackEntry {
pub overlay: OverlayKey,
pub focused: Option<NodeId>,
pub key: Option<Key>,
pub tag: Option<Tag>,
}
#[derive(Clone)]
pub(crate) struct NotifiedFocus {
pub id: NodeId,
pub entry: FocusEntry,
pub on_blur: Option<Callback<()>>,
}
pub(crate) struct FocusRefs<'a> {
pub policy: FocusPolicy,
pub focused: &'a mut Option<NodeId>,
pub focused_key: &'a mut Option<Key>,
pub focused_tag: &'a mut Option<Tag>,
pub focus_stack: &'a mut Vec<FocusStackEntry>,
}
impl FocusRefs<'_> {
fn set_focus(&mut self, tree: &NodeTree, id: NodeId) {
*self.focused = Some(id);
*self.focused_key = tree.node(id).key.clone();
*self.focused_tag = Some(tag_of_node(tree.node(id)));
}
fn clear_focus(&mut self) {
*self.focused = None;
*self.focused_tag = None;
}
}
pub(crate) fn click_target_is_active(
policy: FocusPolicy,
focused: Option<NodeId>,
node_id: NodeId,
focusable: bool,
) -> bool {
!focusable || focused == Some(node_id) || policy == FocusPolicy::Manual
}
pub(crate) fn overlay_ring(tree: &NodeTree, overlay_id: NodeId) -> Vec<NodeId> {
let ring = tree.focusables_in_subtree(overlay_id);
if !ring.is_empty() {
return ring;
}
tree.focusables_in_subtree_unrestricted(overlay_id)
}
pub(crate) fn top_capturing_overlay_is_empty(tree: &NodeTree) -> bool {
tree.top_capturing_overlay()
.is_some_and(|overlay| overlay_ring(tree, overlay.id).is_empty())
}
pub(crate) fn ensure_overlay_focus(tree: &NodeTree, refs: &mut FocusRefs<'_>) -> bool {
let Some(overlay) = tree.top_capturing_overlay() else {
return false;
};
let (overlay_id, overlay_key, auto_focus) =
(overlay.id, OverlayKey::of(overlay), overlay.auto_focus);
let focused_in_overlay = refs
.focused
.is_some_and(|id| tree.is_descendant(overlay_id, id));
push_focus_stack(tree, refs, overlay_key);
if !auto_focus {
if !focused_in_overlay {
return suspend_focus(refs);
}
return false;
}
if focused_in_overlay {
return false;
}
let focusables = overlay_ring(tree, overlay_id);
let Some(&first) = focusables.first() else {
return suspend_focus(refs);
};
refs.set_focus(tree, first);
true
}
fn push_focus_stack(tree: &NodeTree, refs: &mut FocusRefs<'_>, overlay: OverlayKey) {
if let Some(top) = refs.focus_stack.last_mut() {
if top.overlay == overlay {
return;
}
if !overlay_is_live(tree, top.overlay) {
top.overlay = overlay;
return;
}
}
if refs.focus_stack.len() >= MAX_FOCUS_STACK_DEPTH {
refs.focus_stack.remove(0);
}
refs.focus_stack.push(FocusStackEntry {
overlay,
focused: *refs.focused,
key: refs.focused_key.clone(),
tag: *refs.focused_tag,
});
}
fn suspend_focus(refs: &mut FocusRefs<'_>) -> bool {
if refs.focused.is_none() && refs.focused_tag.is_none() {
return false;
}
refs.clear_focus();
true
}
pub(crate) fn restore_focus_from_stack(
tree: &NodeTree,
refs: &mut FocusRefs<'_>,
overlay: OverlayKey,
) -> bool {
let matched = refs
.focus_stack
.iter()
.rposition(|entry| entry.overlay == overlay)
.or_else(|| {
refs.focus_stack
.iter()
.rposition(|entry| !overlay_is_live(tree, entry.overlay))
});
let Some(at) = matched else {
return false;
};
refs.focus_stack.truncate(at + 1);
let Some(saved) = refs.focus_stack.pop() else {
return false;
};
*refs.focused = saved
.focused
.filter(|id| tree.is_valid(*id) && tree.node(*id).is_focusable());
*refs.focused_key = saved.key;
*refs.focused_tag = saved.tag;
focus::restore_focus(
tree,
refs.focused,
refs.focused_key,
refs.focused_tag,
refs.policy,
);
true
}
pub(crate) fn overlay_step(
tree: &NodeTree,
refs: &mut FocusRefs<'_>,
direction: FocusDirection,
) -> bool {
let Some(overlay) = tree.top_capturing_overlay() else {
return false;
};
let focused_in_overlay = refs
.focused
.is_some_and(|id| tree.is_descendant(overlay.id, id));
if !overlay.auto_focus && !focused_in_overlay {
return true;
}
let focusables = overlay_ring(tree, overlay.id);
let Some(target) = focus::ring_step(&focusables, *refs.focused, direction) else {
return true;
};
refs.set_focus(tree, target);
true
}
pub(crate) fn apply_focus_request(
tree: &NodeTree,
refs: &mut FocusRefs<'_>,
request: FocusRequest,
) {
match request {
FocusRequest::Key(key) => {
*refs.focused = None;
*refs.focused_key = Some(key);
*refs.focused_tag = None;
}
FocusRequest::Clear => {
*refs.focused = None;
*refs.focused_key = None;
*refs.focused_tag = None;
}
FocusRequest::Next => {
if !overlay_step(tree, refs, FocusDirection::Next) {
focus::focus_next(tree, refs.focused, refs.focused_key, refs.focused_tag);
}
}
FocusRequest::Prev => {
if !overlay_step(tree, refs, FocusDirection::Prev) {
focus::focus_prev(tree, refs.focused, refs.focused_key, refs.focused_tag);
}
}
}
}
pub(crate) fn notify_focus_change(
tree: &NodeTree,
focused: Option<NodeId>,
last_notified: &mut Option<NotifiedFocus>,
hook: Option<&FocusChangedHook>,
) {
let current = focused.filter(|id| tree.is_valid(*id)).map(|id| {
let node = tree.node(id);
NotifiedFocus {
id,
entry: FocusEntry {
key: node.key.clone(),
tag: tag_of_node(node),
},
on_blur: node.on_blur_callback().cloned(),
}
});
let unchanged = match (&*last_notified, ¤t) {
(None, None) => true,
(Some(old), Some(new)) => {
old.id == new.id
|| old
.entry
.key
.as_ref()
.zip(new.entry.key.as_ref())
.is_some_and(|(old, new)| old == new)
}
_ => false,
};
if unchanged {
*last_notified = current;
return;
}
let previous = last_notified.take();
*last_notified = current.clone();
if let Some(callback) = previous.as_ref().and_then(|old| old.on_blur.clone()) {
callback.emit(());
}
if let Some(callback) = current
.as_ref()
.and_then(|new| tree.node(new.id).on_focus_callback().cloned())
{
callback.emit(());
}
if let Some(hook) = hook {
hook(&FocusChanged {
old: previous.map(|old| old.entry),
new: current.map(|new| new.entry),
});
}
}