use cranpose_core::NodeId;
use crate::{
FocusDirection, LayoutTree, focus_dispatch,
focus_manager::arrow_target,
focus_order::{self, FocusEntry},
layout::LayoutBox,
two_dimensional_focus_search::Arrow,
};
pub fn arrow_focus_target(tree: &LayoutTree, direction: FocusDirection) -> Option<NodeId> {
let arrow = Arrow::of(direction)?;
let root = focus_order::focus_root(tree.root());
let active = focus_dispatch::active_focus_target();
let around_focus = active.map_or_else(Vec::new, |focused| groups_above(root, focused));
let order = focus_order::collect_focus_order(tree);
let mut target = around_focus
.iter()
.find_map(|&group| {
arrow_target(
&focus_order::collect_focus_order_under(tree, group),
active,
arrow,
)
})
.or_else(|| arrow_target(&order, active, arrow))?;
let around_target = groups_above(root, target);
if let Some(restored) = around_target
.iter()
.rev()
.filter(|group| !around_focus.contains(group))
.find_map(|&group| restored_child(root, group, &order))
{
target = restored;
}
if let Some(focused) = active {
for &group in around_focus
.iter()
.filter(|group| !around_target.contains(group))
{
if let Some(restorer) = focus_dispatch::focus_restorer(group) {
restorer.save(focused);
}
}
}
focus_order::set_focus_order(order);
focus_dispatch::note_focus_rect(target);
Some(target)
}
fn restored_child(root: &LayoutBox, group: NodeId, order: &[FocusEntry]) -> Option<NodeId> {
let restorer = focus_dispatch::focus_restorer(group)?;
let group_box = focus_order::find_box(root, group)?;
restorer.restored().find(|&child| {
order.iter().any(|entry| entry.node_id == child)
&& focus_order::find_box(group_box, child).is_some()
})
}
fn groups_above(root: &LayoutBox, node_id: NodeId) -> Vec<NodeId> {
let mut groups = Vec::new();
collect_groups_above(root, node_id, &mut groups);
groups
}
fn collect_groups_above(layout_box: &LayoutBox, node_id: NodeId, groups: &mut Vec<NodeId>) -> bool {
if layout_box.node_id == node_id {
return true;
}
let found = layout_box
.children
.iter()
.any(|child| collect_groups_above(child, node_id, groups));
if found && is_group(layout_box) {
groups.push(layout_box.node_id);
}
found
}
fn is_group(layout_box: &LayoutBox) -> bool {
focus_dispatch::is_focus_group(layout_box.node_id)
|| layout_box
.node_data
.semantics()
.is_some_and(focus_order::is_selectable_group)
}