use crate::core::ObjectId;
use crate::signal::{ConnectionScope, GenericSignal};
pub struct FocusManager {
focused_widget: Option<ObjectId>,
pub focus_changed: GenericSignal,
_connection_scope: ConnectionScope,
focusable_widgets: Vec<ObjectId>,
on_focus_changed: Option<Box<dyn Fn(ObjectId)>>,
traversal_strategy: FocusTraversalStrategy,
widget_positions: std::collections::HashMap<ObjectId, (i32, i32)>,
}
impl FocusManager {
pub fn new() -> Self {
Self {
focused_widget: None,
focus_changed: GenericSignal::new(),
_connection_scope: ConnectionScope::new(),
focusable_widgets: Vec::new(),
on_focus_changed: None,
traversal_strategy: FocusTraversalStrategy::TabOrder,
widget_positions: std::collections::HashMap::new(),
}
}
pub fn focused_widget(&self) -> Option<ObjectId> {
self.focused_widget
}
pub fn set_focus(&mut self, widget_id: ObjectId) -> bool {
if self.focused_widget == Some(widget_id) {
return false;
}
self.focused_widget = Some(widget_id);
self.focus_changed.emit();
if let Some(ref cb) = self.on_focus_changed {
(cb)(widget_id);
}
true
}
pub fn clear_focus(&mut self) -> bool {
if self.focused_widget.is_none() {
return false;
}
let old = self.focused_widget;
self.focused_widget = None;
self.focus_changed.emit();
if let Some(ref cb) = self.on_focus_changed {
if let Some(id) = old {
(cb)(id);
}
}
true
}
pub fn has_focus(&self, widget_id: ObjectId) -> bool {
self.focused_widget == Some(widget_id)
}
pub fn focusable_widgets(&self) -> &[ObjectId] {
&self.focusable_widgets
}
pub fn register_focusable(&mut self, id: ObjectId) {
if !self.focusable_widgets.contains(&id) {
self.focusable_widgets.push(id);
}
}
pub fn unregister_focusable(&mut self, id: ObjectId) {
self.focusable_widgets.retain(|&x| x != id);
if self.focused_widget == Some(id) {
self.focused_widget = None;
self.focus_changed.emit();
}
}
pub fn set_focus_order(&mut self, ids: Vec<ObjectId>) {
self.focusable_widgets = ids;
}
pub fn focus_next(&mut self) -> Option<ObjectId> {
if self.focusable_widgets.is_empty() {
return None;
}
let current = self.focused_widget;
let pos = current.and_then(|id| self.focusable_widgets.iter().position(|&x| x == id));
let next_index = match pos {
Some(idx) => (idx + 1) % self.focusable_widgets.len(),
None => 0,
};
let next = self.focusable_widgets[next_index];
self.set_focus(next);
Some(next)
}
pub fn focus_previous(&mut self) -> Option<ObjectId> {
if self.focusable_widgets.is_empty() {
return None;
}
let current = self.focused_widget;
let pos = current.and_then(|id| self.focusable_widgets.iter().position(|&x| x == id));
let prev_index = match pos {
Some(idx) => {
if idx == 0 {
self.focusable_widgets.len() - 1
} else {
idx - 1
}
}
None => self.focusable_widgets.len() - 1,
};
let prev = self.focusable_widgets[prev_index];
self.set_focus(prev);
Some(prev)
}
pub fn set_a11y_callback(&mut self, cb: Box<dyn Fn(ObjectId)>) {
self.on_focus_changed = Some(cb);
}
}
impl Default for FocusManager {
fn default() -> Self {
Self::new()
}
}
pub enum FocusTraversalStrategy {
TabOrder,
RowMajor,
ColumnMajor,
}
impl FocusManager {
pub fn set_traversal_strategy(&mut self, strategy: FocusTraversalStrategy) {
self.traversal_strategy = strategy;
self.reorder_by_strategy();
}
pub fn set_widget_position(&mut self, id: ObjectId, x: i32, y: i32) {
self.widget_positions.insert(id, (x, y));
if !matches!(self.traversal_strategy, FocusTraversalStrategy::TabOrder) {
self.reorder_by_strategy();
}
}
fn reorder_by_strategy(&mut self) {
match self.traversal_strategy {
FocusTraversalStrategy::TabOrder => {
}
FocusTraversalStrategy::RowMajor => {
self.focusable_widgets.sort_by(|a, b| {
let pos_a = self.widget_positions.get(a).copied().unwrap_or((0, 0));
let pos_b = self.widget_positions.get(b).copied().unwrap_or((0, 0));
pos_a.1.cmp(&pos_b.1).then(pos_a.0.cmp(&pos_b.0))
});
}
FocusTraversalStrategy::ColumnMajor => {
self.focusable_widgets.sort_by(|a, b| {
let pos_a = self.widget_positions.get(a).copied().unwrap_or((0, 0));
let pos_b = self.widget_positions.get(b).copied().unwrap_or((0, 0));
pos_a.0.cmp(&pos_b.0).then(pos_a.1.cmp(&pos_b.1))
});
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_register_focusable() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(20);
fm.register_focusable(30);
assert_eq!(fm.focusable_widgets(), &[10, 20, 30]);
}
#[test]
fn test_register_focusable_duplicate() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(10); assert_eq!(fm.focusable_widgets(), &[10]);
}
#[test]
fn test_focus_next_cycles() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(20);
fm.register_focusable(30);
let focused = fm.focus_next();
assert_eq!(focused, Some(10));
assert_eq!(fm.focused_widget(), Some(10));
assert_eq!(fm.focus_next(), Some(20));
assert_eq!(fm.focus_next(), Some(30));
assert_eq!(fm.focus_next(), Some(10));
}
#[test]
fn test_focus_previous_cycles() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(20);
fm.register_focusable(30);
assert_eq!(fm.focus_previous(), Some(30));
assert_eq!(fm.focus_previous(), Some(20));
assert_eq!(fm.focus_previous(), Some(10));
assert_eq!(fm.focus_previous(), Some(30));
}
#[test]
fn test_unregister_removes() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(20);
fm.register_focusable(30);
fm.unregister_focusable(20);
assert_eq!(fm.focusable_widgets(), &[10, 30]);
}
#[test]
fn test_unregister_clears_focus_if_focused() {
let mut fm = FocusManager::new();
fm.register_focusable(10);
fm.register_focusable(20);
fm.set_focus(10);
assert!(fm.has_focus(10));
fm.unregister_focusable(10);
assert!(fm.focused_widget().is_none());
assert_eq!(fm.focusable_widgets(), &[20]);
}
#[test]
fn test_focus_next_empty_order() {
let mut fm = FocusManager::new();
assert_eq!(fm.focus_next(), None);
}
#[test]
fn test_focus_previous_empty_order() {
let mut fm = FocusManager::new();
assert_eq!(fm.focus_previous(), None);
}
#[test]
fn test_set_focus_order() {
let mut fm = FocusManager::new();
fm.set_focus_order(vec![1, 2, 3, 4]);
assert_eq!(fm.focusable_widgets(), &[1, 2, 3, 4]);
fm.set_focus_order(vec![5, 6]);
assert_eq!(fm.focusable_widgets(), &[5, 6]);
}
#[test]
fn test_a11y_callback_fires() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};
let mut fm = FocusManager::new();
let last_id = Arc::new(AtomicU64::new(0));
let cb_last = Arc::clone(&last_id);
fm.set_a11y_callback(Box::new(move |id| {
cb_last.store(id, Ordering::SeqCst);
}));
fm.set_focus(42);
assert_eq!(last_id.load(Ordering::SeqCst), 42);
fm.set_focus(99);
assert_eq!(last_id.load(Ordering::SeqCst), 99);
}
#[test]
fn test_a11y_callback_fires_on_clear() {
use alloc::sync::Arc;
use core::sync::atomic::{AtomicU64, Ordering};
let mut fm = FocusManager::new();
let last_id = Arc::new(AtomicU64::new(0));
let cb_last = Arc::clone(&last_id);
fm.set_a11y_callback(Box::new(move |id| {
cb_last.store(id, Ordering::SeqCst);
}));
fm.set_focus(77);
assert_eq!(last_id.load(Ordering::SeqCst), 77);
fm.clear_focus();
assert_eq!(last_id.load(Ordering::SeqCst), 77);
}
}