use crate::tree::{WidgetId, WidgetTree};
#[derive(Debug, Default, Clone)]
pub struct FocusManager {
focused: Option<WidgetId>,
trap: Option<WidgetId>,
}
impl FocusManager {
pub fn new() -> Self {
Self::default()
}
pub fn focused(&self) -> Option<WidgetId> {
self.focused
}
pub fn trap(&self) -> Option<WidgetId> {
self.trap
}
pub fn focusable_set(&self, tree: &WidgetTree) -> Vec<WidgetId> {
let order = tree.focus_order();
match self.trap {
None => order,
Some(trap) => order
.into_iter()
.filter(|&id| id == trap || tree.is_descendant(id, trap))
.collect(),
}
}
pub fn focus(&mut self, tree: &WidgetTree, id: WidgetId) -> bool {
if self.focusable_set(tree).contains(&id) {
self.focused = Some(id);
true
} else {
false
}
}
pub fn blur(&mut self) {
self.focused = None;
}
pub fn push_trap(&mut self, tree: &WidgetTree, trap: WidgetId) -> Option<WidgetId> {
self.trap = Some(trap);
let inside = self.focusable_set(tree);
let still_valid = self.focused.map(|f| inside.contains(&f)).unwrap_or(false);
if !still_valid {
self.focused = inside.first().copied();
}
self.focused
}
pub fn pop_trap(&mut self) {
self.trap = None;
}
pub fn focus_next(&mut self, tree: &WidgetTree) -> Option<WidgetId> {
self.step(tree, true)
}
pub fn focus_prev(&mut self, tree: &WidgetTree) -> Option<WidgetId> {
self.step(tree, false)
}
fn step(&mut self, tree: &WidgetTree, forward: bool) -> Option<WidgetId> {
let order = self.focusable_set(tree);
if order.is_empty() {
self.focused = None;
return None;
}
let next = match self
.focused
.and_then(|f| order.iter().position(|&id| id == f))
{
Some(idx) => {
let n = order.len();
if forward {
(idx + 1) % n
} else {
(idx + n - 1) % n
}
}
None => {
if forward {
0
} else {
order.len() - 1
}
}
};
self.focused = order.get(next).copied();
self.focused
}
pub fn autofocus(
&mut self,
tree: &WidgetTree,
is_autofocus: impl Fn(WidgetId) -> bool,
) -> Option<WidgetId> {
let target = self
.focusable_set(tree)
.into_iter()
.find(|&id| is_autofocus(id));
if let Some(id) = target {
self.focused = Some(id);
}
self.focused
}
pub fn reconcile(&mut self, tree: &WidgetTree) -> bool {
if let Some(f) = self.focused {
if !self.focusable_set(tree).contains(&f) {
self.focused = None;
return true;
}
}
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::geometry::Rect;
fn focus_tree() -> (WidgetTree, [WidgetId; 5]) {
let mut t = WidgetTree::new(Rect::ZERO);
let a = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let b = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let modal = t.insert(WidgetId::ROOT, Rect::ZERO).expect("root");
let m1 = t.insert(modal, Rect::ZERO).expect("modal");
let m2 = t.insert(modal, Rect::ZERO).expect("modal");
for id in [a, b, m1, m2] {
if let Some(n) = t.get_mut(id) {
n.focusable = true;
}
}
(t, [a, b, modal, m1, m2])
}
#[test]
fn tab_cycles_with_wraparound() {
let (tree, [a, b, _modal, m1, m2]) = focus_tree();
let mut fm = FocusManager::new();
assert_eq!(fm.focus_next(&tree), Some(a));
assert_eq!(fm.focus_next(&tree), Some(b));
assert_eq!(fm.focus_next(&tree), Some(m1));
assert_eq!(fm.focus_next(&tree), Some(m2));
assert_eq!(fm.focus_next(&tree), Some(a));
}
#[test]
fn shift_tab_goes_backward_and_wraps() {
let (tree, [a, _b, _modal, _m1, m2]) = focus_tree();
let mut fm = FocusManager::new();
assert_eq!(fm.focus_prev(&tree), Some(m2));
fm.focus(&tree, a);
assert_eq!(fm.focus_prev(&tree), Some(m2));
}
#[test]
fn focus_trap_confines_tabbing() {
let (tree, [a, _b, modal, m1, m2]) = focus_tree();
let mut fm = FocusManager::new();
fm.focus(&tree, a);
let landed = fm.push_trap(&tree, modal);
assert_eq!(landed, Some(m1));
assert_eq!(fm.focus_next(&tree), Some(m2));
assert_eq!(fm.focus_next(&tree), Some(m1)); assert!(!fm.focus(&tree, a));
fm.pop_trap();
assert!(fm.focus(&tree, a));
}
#[test]
fn autofocus_picks_first_match() {
let (tree, [_a, b, _modal, _m1, _m2]) = focus_tree();
let mut fm = FocusManager::new();
let focused = fm.autofocus(&tree, |id| id == b);
assert_eq!(focused, Some(b));
}
#[test]
fn reconcile_drops_removed_focus() {
let (mut tree, [a, _b, _modal, _m1, _m2]) = focus_tree();
let mut fm = FocusManager::new();
fm.focus(&tree, a);
assert_eq!(fm.focused(), Some(a));
tree.remove(a);
assert!(fm.reconcile(&tree));
assert_eq!(fm.focused(), None);
}
#[test]
fn blur_clears_focus() {
let (tree, [a, ..]) = focus_tree();
let mut fm = FocusManager::new();
fm.focus(&tree, a);
fm.blur();
assert_eq!(fm.focused(), None);
}
}