use reactive_core::{RwSignal, signal};
pub type FocusId = u64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct FocusHandle(FocusId);
impl FocusHandle {
pub fn request(self) {
request(self.0);
}
pub fn release(self) {
release(self.0);
}
pub fn is_focused(self) -> bool {
is_focused(self.0)
}
}
pub fn handle(id: FocusId) -> FocusHandle {
FocusHandle(id)
}
struct FocusState {
next_id: FocusId,
focused: RwSignal<Option<FocusId>>,
order: Vec<FocusId>,
}
impl FocusState {
fn new() -> Self {
Self {
next_id: 1,
focused: signal(None),
order: Vec::new(),
}
}
}
reactive_core::surface_local! {
slot FOCUS: FocusState = FocusState::new();
access with_focus, with_focus_ref;
context FocusContext, FocusGuard;
}
fn focused_signal() -> RwSignal<Option<FocusId>> {
with_focus_ref(|s| s.focused.clone())
}
pub fn next_id() -> FocusId {
with_focus(|s| {
let id = s.next_id;
s.next_id += 1;
id
})
}
pub fn current() -> Option<FocusId> {
focused_signal().get()
}
pub fn is_focused(id: FocusId) -> bool {
current() == Some(id)
}
pub fn request(id: FocusId) {
let focused = focused_signal();
if focused.peek() != Some(id) {
focused.set(Some(id));
}
}
pub fn release(id: FocusId) {
let focused = focused_signal();
if focused.peek() == Some(id) {
focused.set(None);
}
}
pub fn clear() {
let focused = focused_signal();
if focused.peek().is_some() {
focused.set(None);
}
}
pub fn register(id: FocusId) {
with_focus(|s| {
if !s.order.contains(&id) {
s.order.push(id);
}
});
}
pub fn unregister(id: FocusId) {
with_focus(|s| s.order.retain(|&x| x != id));
release(id);
}
pub fn focus_next() {
step(1);
}
pub fn focus_prev() {
step(-1);
}
fn step(dir: isize) {
let order = with_focus_ref(|s| s.order.clone());
if order.is_empty() {
return;
}
let n = order.len() as isize;
let next = match current().and_then(|c| order.iter().position(|&x| x == c)) {
Some(i) => order[((i as isize + dir).rem_euclid(n)) as usize],
None => {
if dir > 0 {
order[0]
} else {
order[order.len() - 1]
}
}
};
request(next);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_release_and_ids_are_unique() {
clear();
let a = next_id();
let b = next_id();
assert_ne!(a, b, "ids must be unique");
assert!(!is_focused(a));
request(a);
assert!(is_focused(a) && current() == Some(a));
request(b);
assert!(is_focused(b) && !is_focused(a));
release(a);
assert!(is_focused(b));
release(b);
assert!(current().is_none());
}
#[test]
fn tab_order_steps_forward_and_back() {
let (a, b, c) = (next_id(), next_id(), next_id());
register(a);
register(b);
register(c);
request(a);
focus_next();
assert_eq!(current(), Some(b));
focus_next();
assert_eq!(current(), Some(c));
focus_prev();
assert_eq!(current(), Some(b));
unregister(b);
assert!(current().is_none());
unregister(a);
unregister(c);
}
}