use crate::event::Rect;
use crate::types::WidgetId;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct FocusManager {
focused: Option<WidgetId>,
tab_order: Vec<WidgetId>,
}
impl FocusManager {
pub fn new() -> Self {
Self::default()
}
pub fn sync_tab_order(&mut self, stops: &[(WidgetId, Rect)]) {
self.tab_order = stops.iter().map(|(id, _)| *id).collect();
if let Some(cur) = &self.focused {
if !self.tab_order.contains(cur) {
self.focused = None;
}
}
}
pub fn tab_order(&self) -> &[WidgetId] {
&self.tab_order
}
pub fn focused(&self) -> Option<&WidgetId> {
self.focused.as_ref()
}
pub fn is_focused(&self, id: &WidgetId) -> bool {
self.focused.as_ref() == Some(id)
}
pub fn focus_next(&mut self) -> bool {
self.step(1)
}
pub fn focus_prev(&mut self) -> bool {
self.step(-1)
}
fn step(&mut self, dir: i32) -> bool {
if self.tab_order.is_empty() {
return self.set_focus(None);
}
let len = self.tab_order.len() as i32;
let next_idx = match &self.focused {
None if dir >= 0 => 0,
None => len - 1,
Some(cur) => {
let idx = self.tab_order.iter().position(|w| w == cur).unwrap_or(0) as i32;
(idx + dir).rem_euclid(len)
}
};
let next = self.tab_order[next_idx as usize];
self.set_focus(Some(next))
}
pub fn set_focus(&mut self, id: Option<WidgetId>) -> bool {
if self.focused != id {
self.focused = id;
true
} else {
false
}
}
pub fn clear(&mut self) -> bool {
self.set_focus(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stops(ids: &[&str]) -> Vec<(WidgetId, Rect)> {
ids.iter()
.enumerate()
.map(|(i, id)| {
(
WidgetId::new(*id),
Rect::new(0.0, i as f32 * 3.0, 10.0, 3.0),
)
})
.collect()
}
#[test]
fn new_manager_has_no_focus() {
let fm = FocusManager::new();
assert_eq!(fm.focused(), None);
assert!(fm.tab_order().is_empty());
}
#[test]
fn sync_tab_order_populates_order() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
assert_eq!(
fm.tab_order(),
&[WidgetId::new("a"), WidgetId::new("b"), WidgetId::new("c")]
);
}
#[test]
fn focus_next_from_none_lands_on_first() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
assert!(fm.focus_next());
assert_eq!(fm.focused(), Some(&WidgetId::new("a")));
}
#[test]
fn focus_prev_from_none_lands_on_last() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
assert!(fm.focus_prev());
assert_eq!(fm.focused(), Some(&WidgetId::new("c")));
}
#[test]
fn focus_next_wraps() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
fm.focus_next(); fm.focus_next(); fm.focus_next(); assert_eq!(fm.focused(), Some(&WidgetId::new("c")));
fm.focus_next(); assert_eq!(fm.focused(), Some(&WidgetId::new("a")));
}
#[test]
fn focus_prev_wraps() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
fm.focus_next(); fm.focus_prev(); assert_eq!(fm.focused(), Some(&WidgetId::new("c")));
}
#[test]
fn focus_next_noop_on_empty_order() {
let mut fm = FocusManager::new();
assert!(!fm.focus_next());
assert_eq!(fm.focused(), None);
}
#[test]
fn set_focus_returns_whether_changed() {
let mut fm = FocusManager::new();
assert!(fm.set_focus(Some(WidgetId::new("a"))));
assert!(
!fm.set_focus(Some(WidgetId::new("a"))),
"same id is a no-op"
);
assert!(fm.set_focus(Some(WidgetId::new("b"))));
assert!(fm.clear());
assert!(!fm.clear(), "clearing an already-clear focus is a no-op");
}
#[test]
fn sync_tab_order_preserves_focus_by_id() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
fm.set_focus(Some(WidgetId::new("b")));
fm.sync_tab_order(&stops(&["b", "a", "c"]));
assert_eq!(
fm.focused(),
Some(&WidgetId::new("b")),
"focus follows the id, not the index"
);
}
#[test]
fn sync_tab_order_clears_focus_when_id_removed() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b", "c"]));
fm.set_focus(Some(WidgetId::new("b")));
fm.sync_tab_order(&stops(&["a", "c"]));
assert_eq!(fm.focused(), None);
}
#[test]
fn is_focused_checks_current_id() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["a", "b"]));
fm.set_focus(Some(WidgetId::new("a")));
assert!(fm.is_focused(&WidgetId::new("a")));
assert!(!fm.is_focused(&WidgetId::new("b")));
}
#[test]
fn single_stop_next_and_prev_stay_put() {
let mut fm = FocusManager::new();
fm.sync_tab_order(&stops(&["only"]));
assert!(fm.focus_next());
assert_eq!(fm.focused(), Some(&WidgetId::new("only")));
assert!(
!fm.focus_next(),
"focusing the same single stop again is a no-op"
);
assert!(!fm.focus_prev());
}
}