use crate::event::UiTarget;
use crate::state::UiState;
use crate::tree::{El, Kind, Rect};
pub fn arrow_nav_group(root: &El, ui_state: &UiState, focused_id: &str) -> Option<Vec<UiTarget>> {
find_group(root, ui_state, None, focused_id)
}
fn find_group(
node: &El,
ui_state: &UiState,
inherited_clip: Option<Rect>,
focused_id: &str,
) -> Option<Vec<UiTarget>> {
let computed = ui_state.rect(&node.computed_id);
let clip = if node.clip {
match inherited_clip {
Some(clip) => Some(
clip.intersect(computed)
.unwrap_or(Rect::new(0.0, 0.0, 0.0, 0.0)),
),
None => Some(computed),
}
} else {
inherited_clip
};
if node.arrow_nav_siblings && node.children.iter().any(|c| c.computed_id == focused_id) {
let mut siblings: Vec<UiTarget> = Vec::new();
for child in &node.children {
collect_focusable_self(child, ui_state, clip, &mut siblings);
}
return Some(siblings);
}
for child in &node.children {
if let Some(group) = find_group(child, ui_state, clip, focused_id) {
return Some(group);
}
}
None
}
fn collect_focusable_self(
node: &El,
ui_state: &UiState,
clip: Option<Rect>,
out: &mut Vec<UiTarget>,
) {
let computed = ui_state.rect(&node.computed_id);
if node.focusable
&& let Some(key) = &node.key
&& clip
.map(|c| c.intersect(computed).is_some())
.unwrap_or(true)
{
out.push(UiTarget {
key: key.clone(),
node_id: node.computed_id.clone(),
rect: computed,
});
}
}
pub fn focus_order(root: &El, ui_state: &UiState) -> Vec<UiTarget> {
let mut out = Vec::new();
collect_focus(root, ui_state, None, &mut out);
out
}
pub fn selection_order(root: &El, ui_state: &UiState) -> Vec<UiTarget> {
let mut out = Vec::new();
collect_selectable(root, ui_state, None, &mut out);
out
}
fn collect_selectable(
node: &El,
ui_state: &UiState,
inherited_clip: Option<Rect>,
out: &mut Vec<UiTarget>,
) {
let computed = ui_state.rect(&node.computed_id);
let clip = if node.clip {
match inherited_clip {
Some(clip) => Some(
clip.intersect(computed)
.unwrap_or(Rect::new(0.0, 0.0, 0.0, 0.0)),
),
None => Some(computed),
}
} else {
inherited_clip
};
if node.selectable
&& let Some(key) = &node.key
&& clip
.map(|c| c.intersect(computed).is_some())
.unwrap_or(true)
{
out.push(UiTarget {
key: key.clone(),
node_id: node.computed_id.clone(),
rect: computed,
});
}
for child in &node.children {
collect_selectable(child, ui_state, clip, out);
}
}
fn collect_focus(
node: &El,
ui_state: &UiState,
inherited_clip: Option<Rect>,
out: &mut Vec<UiTarget>,
) {
let computed = ui_state.rect(&node.computed_id);
let clip = if node.clip {
match inherited_clip {
Some(clip) => Some(
clip.intersect(computed)
.unwrap_or(Rect::new(0.0, 0.0, 0.0, 0.0)),
),
None => Some(computed),
}
} else {
inherited_clip
};
if node.focusable
&& let Some(key) = &node.key
&& clip
.map(|c| c.intersect(computed).is_some())
.unwrap_or(true)
{
out.push(UiTarget {
key: key.clone(),
node_id: node.computed_id.clone(),
rect: computed,
});
}
for child in &node.children {
collect_focus(child, ui_state, clip, out);
}
}
pub fn sync_popover_focus(root: &El, ui_state: &mut UiState) {
let new_layers = collect_popover_layer_ids(root);
let old_layers = std::mem::take(&mut ui_state.popover_focus.layer_ids);
for id in old_layers.iter().rev() {
if !new_layers.contains(id) {
let saved = ui_state.popover_focus.focus_stack.pop();
if ui_state.focused.is_none()
&& let Some(target) = saved
&& ui_state
.focus
.order
.iter()
.any(|t| t.node_id == target.node_id)
{
ui_state.focused = Some(target);
}
}
}
for id in &new_layers {
if !old_layers.contains(id) {
if let Some(current) = ui_state.focused.clone() {
ui_state.popover_focus.focus_stack.push(current);
}
if let Some(first) = first_focusable_in(root, id, ui_state) {
ui_state.focused = Some(first);
}
}
}
ui_state.popover_focus.layer_ids = new_layers;
}
fn collect_popover_layer_ids(root: &El) -> Vec<String> {
let mut out = Vec::new();
walk_popover_layers(root, &mut out);
out
}
fn walk_popover_layers(node: &El, out: &mut Vec<String>) {
if matches!(node.kind, Kind::Custom("popover_layer")) {
out.push(node.computed_id.clone());
}
for child in &node.children {
walk_popover_layers(child, out);
}
}
fn first_focusable_in(root: &El, layer_id: &str, ui_state: &UiState) -> Option<UiTarget> {
let (subtree, inherited_clip) = locate_subtree(root, ui_state, None, layer_id)?;
let mut out = Vec::new();
collect_focus(subtree, ui_state, inherited_clip, &mut out);
out.into_iter().next()
}
fn locate_subtree<'a>(
node: &'a El,
ui_state: &UiState,
inherited_clip: Option<Rect>,
target_id: &str,
) -> Option<(&'a El, Option<Rect>)> {
let computed = ui_state.rect(&node.computed_id);
let clip = if node.clip {
match inherited_clip {
Some(clip) => Some(
clip.intersect(computed)
.unwrap_or(Rect::new(0.0, 0.0, 0.0, 0.0)),
),
None => Some(computed),
}
} else {
inherited_clip
};
if node.computed_id == target_id {
return Some((node, clip));
}
for child in &node.children {
if let Some(found) = locate_subtree(child, ui_state, clip, target_id) {
return Some(found);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::layout;
use crate::state::UiState;
use crate::tree::*;
use crate::{button, column, row};
#[test]
fn focus_order_collects_keyed_focusable_nodes() {
let mut tree = column([
crate::text("0"),
row([button("-").key("dec"), button("+").key("inc")]),
])
.padding(20.0);
let mut state = UiState::new();
layout(&mut tree, &mut state, Rect::new(0.0, 0.0, 400.0, 200.0));
let order = focus_order(&tree, &state);
let keys: Vec<&str> = order.iter().map(|t| t.key.as_str()).collect();
assert_eq!(keys, vec!["dec", "inc"]);
}
}