#[derive(Debug, Clone)]
pub struct FocusState {
pub is_focused: bool,
}
#[derive(Debug, Clone, Default)]
pub struct UseFocusOptions {
pub auto_focus: bool,
pub is_active: bool,
pub id: Option<String>,
}
impl UseFocusOptions {
pub fn new() -> Self {
Self {
auto_focus: false,
is_active: true,
id: None,
}
}
pub fn auto_focus(mut self) -> Self {
self.auto_focus = true;
self
}
pub fn is_active(mut self, active: bool) -> Self {
self.is_active = active;
self
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.id = Some(id.into());
self
}
}
#[derive(Debug, Clone)]
struct FocusableElement {
id: usize,
custom_id: Option<String>,
is_active: bool,
}
#[derive(Debug, Default)]
pub struct FocusManager {
elements: Vec<FocusableElement>,
focused_index: Option<usize>,
next_id: usize,
}
impl FocusManager {
pub fn new() -> Self {
Self {
elements: Vec::new(),
focused_index: None,
next_id: 1,
}
}
pub fn register(
&mut self,
custom_id: Option<String>,
is_active: bool,
auto_focus: bool,
) -> usize {
let id = self.next_id;
self.next_id = self
.next_id
.checked_add(1)
.filter(|next| *next != 0)
.unwrap_or_else(|| panic!("FocusManager ID counter exhausted"));
self.elements.push(FocusableElement {
id,
custom_id,
is_active,
});
if auto_focus && self.focused_index.is_none() && is_active {
self.focused_index = Some(self.elements.len() - 1);
}
id
}
pub fn unregister(&mut self, id: usize) {
if let Some(pos) = self.elements.iter().position(|e| e.id == id) {
self.elements.remove(pos);
if let Some(focused) = self.focused_index {
if pos == focused {
self.focused_index = None;
} else if pos < focused {
self.focused_index = Some(focused - 1);
}
}
}
}
pub fn update(
&mut self,
id: usize,
custom_id: Option<String>,
is_active: bool,
auto_focus: bool,
) {
if let Some(elem) = self.elements.iter_mut().find(|e| e.id == id) {
elem.custom_id = custom_id;
elem.is_active = is_active;
}
if auto_focus && self.focused_index.is_none() && is_active {
if let Some(pos) = self.elements.iter().position(|e| e.id == id) {
self.focused_index = Some(pos);
}
}
}
pub fn is_focused(&self, id: usize) -> bool {
self.focused_index
.and_then(|idx| self.elements.get(idx))
.map(|e| e.id == id)
.unwrap_or(false)
}
pub fn focus_next(&mut self) {
let active_elements: Vec<usize> = self
.elements
.iter()
.enumerate()
.filter(|(_, e)| e.is_active)
.map(|(i, _)| i)
.collect();
if active_elements.is_empty() {
return;
}
let current = self.focused_index.unwrap_or(0);
let current_pos = active_elements
.iter()
.position(|&i| i == current)
.unwrap_or(0);
let next_pos = (current_pos + 1) % active_elements.len();
self.focused_index = Some(active_elements[next_pos]);
}
pub fn focus_previous(&mut self) {
let active_elements: Vec<usize> = self
.elements
.iter()
.enumerate()
.filter(|(_, e)| e.is_active)
.map(|(i, _)| i)
.collect();
if active_elements.is_empty() {
return;
}
let current = self.focused_index.unwrap_or(0);
let current_pos = active_elements
.iter()
.position(|&i| i == current)
.unwrap_or(0);
let prev_pos = if current_pos == 0 {
active_elements.len() - 1
} else {
current_pos - 1
};
self.focused_index = Some(active_elements[prev_pos]);
}
pub fn focus(&mut self, custom_id: &str) {
if let Some(pos) = self
.elements
.iter()
.position(|e| e.custom_id.as_deref() == Some(custom_id) && e.is_active)
{
self.focused_index = Some(pos);
}
}
pub fn enable_focus(&mut self, id: usize, enabled: bool) {
if let Some(elem) = self.elements.iter_mut().find(|e| e.id == id) {
elem.is_active = enabled;
}
}
pub fn clear(&mut self) {
self.elements.clear();
}
}
pub fn use_focus(options: UseFocusOptions) -> FocusState {
use crate::hooks::use_signal;
let registration = use_signal(|| {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().register(
options.id.clone(),
options.is_active,
options.auto_focus,
)
} else {
0 }
});
let id = registration.get();
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().update(
id,
options.id.clone(),
options.is_active,
options.auto_focus,
);
}
crate::hooks::use_effect_once({
move || {
Some(Box::new(move || {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().unregister(id);
}
}))
}
});
let is_focused = crate::runtime::current_runtime()
.map(|ctx| ctx.borrow().focus_manager().is_focused(id))
.unwrap_or(false);
FocusState { is_focused }
}
pub fn use_focus_manager() -> FocusManagerHandle {
FocusManagerHandle
}
#[derive(Clone, Copy)]
pub struct FocusManagerHandle;
impl FocusManagerHandle {
pub fn focus_next(&self) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().focus_next();
}
}
pub fn focus_previous(&self) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().focus_previous();
}
}
pub fn focus(&self, id: &str) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut().focus_manager_mut().focus(id);
}
}
pub fn enable_focus(&self, id: usize, enabled: bool) {
if let Some(ctx) = crate::runtime::current_runtime() {
ctx.borrow_mut()
.focus_manager_mut()
.enable_focus(id, enabled);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_focus_manager_registration() {
let mut fm = FocusManager::new();
let id1 = fm.register(None, true, false);
let id2 = fm.register(None, true, false);
assert!(id1 != id2);
assert_eq!(fm.elements.len(), 2);
}
#[test]
fn test_focus_manager_auto_focus() {
let mut fm = FocusManager::new();
let id1 = fm.register(None, true, true); let _id2 = fm.register(None, true, false);
assert!(fm.is_focused(id1));
}
#[test]
fn test_focus_navigation() {
let mut fm = FocusManager::new();
let id1 = fm.register(None, true, true);
let id2 = fm.register(None, true, false);
let id3 = fm.register(None, true, false);
assert!(fm.is_focused(id1));
fm.focus_next();
assert!(fm.is_focused(id2));
fm.focus_next();
assert!(fm.is_focused(id3));
fm.focus_next();
assert!(fm.is_focused(id1));
fm.focus_previous();
assert!(fm.is_focused(id3));
}
#[test]
fn test_focus_by_id() {
let mut fm = FocusManager::new();
let _id1 = fm.register(Some("first".to_string()), true, true);
let id2 = fm.register(Some("second".to_string()), true, false);
fm.focus("second");
assert!(fm.is_focused(id2));
}
#[test]
fn test_inactive_elements_skipped() {
let mut fm = FocusManager::new();
let id1 = fm.register(None, true, true);
let _id2 = fm.register(None, false, false); let id3 = fm.register(None, true, false);
assert!(fm.is_focused(id1));
fm.focus_next();
assert!(fm.is_focused(id3)); }
#[test]
fn test_focus_with_runtime() {
use crate::runtime::{RuntimeContext, with_runtime};
use std::cell::RefCell;
use std::rc::Rc;
let ctx = Rc::new(RefCell::new(RuntimeContext::new()));
with_runtime(ctx.clone(), || {
let fm_handle = use_focus_manager();
let id1 = ctx
.borrow_mut()
.focus_manager_mut()
.register(None, true, true);
let id2 = ctx
.borrow_mut()
.focus_manager_mut()
.register(None, true, false);
assert!(ctx.borrow().focus_manager().is_focused(id1));
fm_handle.focus_next();
assert!(ctx.borrow().focus_manager().is_focused(id2));
});
}
}