use crate::callback::ScopeId;
use crate::core::element::Key;
use crate::core::node::{NodeId, NodeKind, NodeTree};
use crate::layout::tag::{Tag, tag_of_node};
pub(crate) fn scope_for_node(tree: &NodeTree, id: NodeId) -> Option<ScopeId> {
let mut current = Some(id);
while let Some(id) = current {
if !tree.is_valid(id) {
break;
}
let node = tree.node(id);
if let NodeKind::Group(group) = &node.kind {
return Some(group.scope);
}
current = node.parent;
}
None
}
pub(crate) fn restore_focus(
tree: &NodeTree,
focused: &mut Option<NodeId>,
focused_key: &mut Option<Key>,
focused_tag: &mut Option<Tag>,
) {
if let Some(id) = *focused
&& tree.is_valid(id)
&& tree.node(id).is_focusable()
{
*focused_key = tree.node(id).key.clone();
*focused_tag = Some(tag_of_node(tree.node(id)));
return;
}
if let Some(key) = focused_key {
if let Some(id) = tree
.iter_with_overlays()
.find(|n| n.key.as_ref() == Some(key))
.map(|n| n.id)
{
if tree.node(id).is_focusable() {
*focused = Some(id);
*focused_tag = Some(tag_of_node(tree.node(id)));
return;
}
if let Some(focusable_id) = find_first_focusable_descendant(tree, id) {
*focused = Some(focusable_id);
*focused_tag = Some(tag_of_node(tree.node(focusable_id)));
return;
}
}
}
if let Some(tag) = *focused_tag
&& let Some(id) = tree
.iter_with_overlays()
.find(|n| n.is_focusable() && tag_of_node(n) == tag)
.map(|n| n.id)
{
*focused = Some(id);
*focused_key = tree.node(id).key.clone();
return;
}
*focused = tree.iter().find(|n| n.is_focusable()).map(|n| n.id);
if let Some(id) = *focused {
*focused_key = tree.node(id).key.clone();
*focused_tag = Some(tag_of_node(tree.node(id)));
} else {
*focused_key = None;
*focused_tag = None;
}
}
pub(crate) fn focus_next(
tree: &NodeTree,
focused: &mut Option<NodeId>,
focused_key: &mut Option<Key>,
focused_tag: &mut Option<Tag>,
) {
let focusables = tree.focusables();
if focusables.is_empty() {
return;
}
let next = if let Some(curr) = *focused
&& let Some(idx) = focusables.iter().position(|id| *id == curr)
{
focusables[(idx + 1) % focusables.len()]
} else {
focusables[0]
};
*focused = Some(next);
*focused_key = tree.node(next).key.clone();
*focused_tag = Some(tag_of_node(tree.node(next)));
}
pub(crate) fn focus_prev(
tree: &NodeTree,
focused: &mut Option<NodeId>,
focused_key: &mut Option<Key>,
focused_tag: &mut Option<Tag>,
) {
let focusables = tree.focusables();
if focusables.is_empty() {
return;
}
let prev = if let Some(curr) = *focused
&& let Some(idx) = focusables.iter().position(|id| *id == curr)
{
focusables[(idx + focusables.len().saturating_sub(1)) % focusables.len()]
} else {
focusables[focusables.len().saturating_sub(1)]
};
*focused = Some(prev);
*focused_key = tree.node(prev).key.clone();
*focused_tag = Some(tag_of_node(tree.node(prev)));
}
pub(crate) fn find_first_focusable_descendant(tree: &NodeTree, root: NodeId) -> Option<NodeId> {
let mut queue = std::collections::VecDeque::new();
queue.push_back(root);
while let Some(current_id) = queue.pop_front() {
let node = tree.node(current_id);
for &child in &node.children {
if !tree.is_valid(child) {
continue;
}
let child_node = tree.node(child);
if child_node.is_focusable() {
return Some(child);
}
queue.push_back(child);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widgets::Button;
fn alloc_node(tree: &mut NodeTree, parent: Option<NodeId>, focusable: bool) -> NodeId {
let id = tree.alloc();
let epoch = tree.node(tree.root).epoch; let node = tree.node_mut(id);
node.parent = parent;
node.epoch = epoch;
if focusable {
node.kind = NodeKind::from(Button::new("btn"));
}
id
}
fn build_tree_with_focusable_children(n: usize) -> (NodeTree, NodeId, Vec<NodeId>) {
let mut tree = NodeTree::new();
let epoch = tree.begin_epoch();
let root = tree.alloc();
tree.root = root;
{
let r = tree.node_mut(root);
r.epoch = epoch;
}
let mut children = Vec::new();
for _ in 0..n {
let child = alloc_node(&mut tree, Some(root), true);
children.push(child);
}
tree.node_mut(root).children = children.clone();
(tree, root, children)
}
#[test]
fn focus_next_wraps_around_at_end() {
let (tree, _root, children) = build_tree_with_focusable_children(3);
let mut focused = Some(children[2]); let mut key = None;
let mut tag = None;
focus_next(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[0]), "should wrap to first focusable");
}
#[test]
fn focus_prev_wraps_around_at_start() {
let (tree, _root, children) = build_tree_with_focusable_children(3);
let mut focused = Some(children[0]); let mut key = None;
let mut tag = None;
focus_prev(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[2]), "should wrap to last focusable");
}
#[test]
fn focus_next_selects_first_when_none_focused() {
let (tree, _root, children) = build_tree_with_focusable_children(3);
let mut focused: Option<NodeId> = None;
let mut key = None;
let mut tag = None;
focus_next(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[0]));
}
#[test]
fn focus_prev_selects_last_when_none_focused() {
let (tree, _root, children) = build_tree_with_focusable_children(3);
let mut focused: Option<NodeId> = None;
let mut key = None;
let mut tag = None;
focus_prev(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[2]));
}
#[test]
fn focus_next_noop_on_empty_tree() {
let mut tree = NodeTree::new();
tree.begin_epoch();
let mut focused: Option<NodeId> = None;
let mut key = None;
let mut tag = None;
focus_next(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, None);
}
#[test]
fn find_first_focusable_descendant_breadth_first() {
let mut tree = NodeTree::new();
let epoch = tree.begin_epoch();
let root = tree.alloc();
tree.root = root;
tree.node_mut(root).epoch = epoch;
let child_a = alloc_node(&mut tree, Some(root), false);
let grandchild = alloc_node(&mut tree, Some(child_a), true);
tree.node_mut(child_a).children = vec![grandchild];
let child_b = alloc_node(&mut tree, Some(root), true);
tree.node_mut(root).children = vec![child_a, child_b];
let result = find_first_focusable_descendant(&tree, root);
assert_eq!(
result,
Some(child_b),
"BFS should find shallower child_b first"
);
}
#[test]
fn find_first_focusable_descendant_returns_none_when_no_focusable() {
let mut tree = NodeTree::new();
let epoch = tree.begin_epoch();
let root = tree.alloc();
tree.root = root;
tree.node_mut(root).epoch = epoch;
let child = alloc_node(&mut tree, Some(root), false);
tree.node_mut(root).children = vec![child];
assert_eq!(find_first_focusable_descendant(&tree, root), None);
}
#[test]
fn restore_focus_keeps_valid_focused_node() {
let (tree, _root, children) = build_tree_with_focusable_children(2);
let mut focused = Some(children[1]);
let mut key = None;
let mut tag = None;
restore_focus(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(
focused,
Some(children[1]),
"valid focusable node should be kept"
);
assert_eq!(tag, Some(Tag::Button));
}
#[test]
fn restore_focus_falls_back_to_key_match() {
let (mut tree, _root, children) = build_tree_with_focusable_children(2);
let the_key: Key = "my-btn".into();
tree.node_mut(children[0]).key = Some(the_key.clone());
let mut focused = Some(NodeId::INVALID);
let mut key = Some(the_key);
let mut tag = None;
restore_focus(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[0]), "should restore by key");
}
#[test]
fn restore_focus_falls_back_to_tag_match() {
let (tree, _root, children) = build_tree_with_focusable_children(2);
let mut focused = Some(NodeId::INVALID);
let mut key = None;
let mut tag = Some(Tag::Button);
restore_focus(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[0]));
}
#[test]
fn restore_focus_falls_back_to_first_focusable() {
let (tree, _root, children) = build_tree_with_focusable_children(2);
let mut focused = Some(NodeId::INVALID);
let mut key = None;
let mut tag = None;
restore_focus(&tree, &mut focused, &mut key, &mut tag);
assert_eq!(focused, Some(children[0]));
}
}