use crate::event::UiTarget;
use crate::state::UiState;
use crate::tree::{ArrowNav, El, Kind, Rect};
pub fn arrow_nav_group(root: &El, focused_id: &str) -> Option<(ArrowNav, Vec<UiTarget>)> {
find_group(root, None, focused_id)
}
fn find_group(
node: &El,
inherited_clip: Option<Rect>,
focused_id: &str,
) -> Option<(ArrowNav, Vec<UiTarget>)> {
let computed = node.computed_rect;
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 let Some(mode) = node.arrow_nav {
let mut members: Vec<UiTarget> = Vec::new();
if mode == ArrowNav::Grid {
for child in &node.children {
collect_focusable_descendants(child, clip, &mut members);
}
} else {
for child in &node.children {
collect_focusable_self(child, clip, &mut members);
}
}
if members.iter().any(|t| t.node_id == focused_id.into()) {
return Some((mode, members));
}
}
for child in &node.children {
if let Some(group) = find_group(child, clip, focused_id) {
return Some(group);
}
}
None
}
fn collect_focusable_descendants(node: &El, inherited_clip: Option<Rect>, out: &mut Vec<UiTarget>) {
let computed = node.computed_rect;
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
};
collect_focusable_self(node, clip, out);
for child in &node.children {
collect_focusable_descendants(child, clip, out);
}
}
fn collect_focusable_self(node: &El, clip: Option<Rect>, out: &mut Vec<UiTarget>) {
let computed = node.computed_rect;
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,
tooltip: node.tooltip.clone(),
scroll_offset_y: 0.0,
});
}
}
pub fn focus_order(root: &El) -> Vec<UiTarget> {
let mut out = Vec::new();
collect_focus(root, None, &mut out);
out
}
pub fn selection_order(root: &El) -> Vec<UiTarget> {
let mut out = Vec::new();
collect_selectable(root, None, &mut out);
out
}
pub fn focus_and_selection_order(root: &El) -> (Vec<UiTarget>, Vec<UiTarget>) {
let mut focus = Vec::new();
let mut selection = Vec::new();
collect_orders(root, None, &mut focus, &mut selection);
(focus, selection)
}
fn collect_orders(
node: &El,
inherited_clip: Option<Rect>,
focus: &mut Vec<UiTarget>,
selection: &mut Vec<UiTarget>,
) {
let computed = node.computed_rect;
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 || node.selectable)
&& let Some(key) = &node.key
&& clip
.map(|c| c.intersect(computed).is_some())
.unwrap_or(true)
{
let target = UiTarget {
key: key.clone(),
node_id: node.computed_id.clone(),
rect: computed,
tooltip: node.tooltip.clone(),
scroll_offset_y: 0.0,
};
if node.selectable {
selection.push(target.clone());
}
if node.focusable {
focus.push(target);
}
}
for child in &node.children {
collect_orders(child, clip, focus, selection);
}
}
fn collect_selectable(node: &El, inherited_clip: Option<Rect>, out: &mut Vec<UiTarget>) {
let computed = node.computed_rect;
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,
tooltip: node.tooltip.clone(),
scroll_offset_y: 0.0,
});
}
for child in &node.children {
collect_selectable(child, clip, out);
}
}
fn collect_focus(node: &El, inherited_clip: Option<Rect>, out: &mut Vec<UiTarget>) {
let computed = node.computed_rect;
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,
tooltip: node.tooltip.clone(),
scroll_offset_y: 0.0,
});
}
for child in &node.children {
collect_focus(child, 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.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.to_string());
}
for child in &node.children {
walk_popover_layers(child, out);
}
}
fn first_focusable_in(root: &El, layer_id: &str) -> Option<UiTarget> {
let (subtree, inherited_clip) = locate_subtree(root, None, layer_id)?;
let mut out = Vec::new();
collect_focus(subtree, inherited_clip, &mut out);
out.into_iter().next()
}
fn locate_subtree<'a>(
node: &'a El,
inherited_clip: Option<Rect>,
target_id: &str,
) -> Option<(&'a El, Option<Rect>)> {
let computed = node.computed_rect;
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.into() {
return Some((node, clip));
}
for child in &node.children {
if let Some(found) = locate_subtree(child, 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);
let keys: Vec<&str> = order.iter().map(|t| t.key.as_str()).collect();
assert_eq!(keys, vec!["dec", "inc"]);
}
}