use super::{
super::lock::{read_or_recover, write_or_recover},
AccessibleNode, Announcement,
};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
pub struct AccessibilityManager {
nodes: HashMap<String, AccessibleNode>,
root: Option<String>,
focus: Option<String>,
announcements: Vec<Announcement>,
enabled: bool,
reduce_motion: bool,
high_contrast: bool,
}
impl AccessibilityManager {
pub fn new() -> Self {
Self {
nodes: HashMap::new(),
root: None,
focus: None,
announcements: Vec::new(),
enabled: true,
reduce_motion: false,
high_contrast: false,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn set_reduce_motion(&mut self, value: bool) {
self.reduce_motion = value;
}
pub fn prefers_reduced_motion(&self) -> bool {
self.reduce_motion
}
pub fn set_high_contrast(&mut self, value: bool) {
self.high_contrast = value;
}
pub fn is_high_contrast(&self) -> bool {
self.high_contrast
}
pub fn set_root(&mut self, id: impl Into<String>) {
self.root = Some(id.into());
}
pub fn root(&self) -> Option<&AccessibleNode> {
self.root.as_ref().and_then(|id| self.nodes.get(id))
}
pub fn add_node(&mut self, node: AccessibleNode) {
self.nodes.insert(node.id.clone(), node);
}
pub fn remove_node(&mut self, id: &str) -> Option<AccessibleNode> {
self.nodes.remove(id)
}
pub fn get_node(&self, id: &str) -> Option<&AccessibleNode> {
self.nodes.get(id)
}
pub fn get_node_mut(&mut self, id: &str) -> Option<&mut AccessibleNode> {
self.nodes.get_mut(id)
}
pub fn update_state(&mut self, id: &str, state: crate::utils::accessibility::AccessibleState) {
if let Some(node) = self.nodes.get_mut(id) {
node.state = state;
}
}
pub fn set_focus(&mut self, id: impl Into<String>) {
let id = id.into();
if let Some(old_id) = &self.focus {
if let Some(node) = self.nodes.get_mut(old_id) {
node.state.focused = false;
}
}
if let Some(node) = self.nodes.get_mut(&id) {
if node.is_focusable() {
node.state.focused = true;
self.focus = Some(id);
}
}
}
pub fn focus(&self) -> Option<&str> {
self.focus.as_deref()
}
pub fn focused_node(&self) -> Option<&AccessibleNode> {
self.focus.as_ref().and_then(|id| self.nodes.get(id))
}
pub fn focus_next(&mut self) -> Option<&str> {
let focusable: Vec<_> = self
.nodes
.values()
.filter(|n| n.is_focusable())
.map(|n| &n.id)
.collect();
if focusable.is_empty() {
return None;
}
let current_idx = self
.focus
.as_ref()
.and_then(|id| focusable.iter().position(|fid| *fid == id))
.unwrap_or(0);
let next_idx = (current_idx + 1) % focusable.len();
let next_id = focusable[next_idx].clone();
self.set_focus(&next_id);
self.focus.as_deref()
}
pub fn focus_prev(&mut self) -> Option<&str> {
let focusable: Vec<_> = self
.nodes
.values()
.filter(|n| n.is_focusable())
.map(|n| &n.id)
.collect();
if focusable.is_empty() {
return None;
}
let current_idx = self
.focus
.as_ref()
.and_then(|id| focusable.iter().position(|fid| *fid == id))
.unwrap_or(0);
let prev_idx = if current_idx == 0 {
focusable.len() - 1
} else {
current_idx - 1
};
let prev_id = focusable[prev_idx].clone();
self.set_focus(&prev_id);
self.focus.as_deref()
}
pub fn announce(&mut self, announcement: Announcement) {
if self.enabled {
self.announcements.push(announcement);
}
}
pub fn announce_polite(&mut self, message: impl Into<String>) {
self.announce(Announcement::polite(message));
}
pub fn announce_assertive(&mut self, message: impl Into<String>) {
self.announce(Announcement::assertive(message));
}
pub fn pending_announcements(&self) -> &[Announcement] {
&self.announcements
}
pub fn clear_announcements(&mut self) {
self.announcements.clear();
}
pub fn focusable_nodes(&self) -> Vec<&AccessibleNode> {
self.nodes.values().filter(|n| n.is_focusable()).collect()
}
pub fn landmarks(&self) -> Vec<&AccessibleNode> {
self.nodes
.values()
.filter(|n| n.role.is_landmark())
.collect()
}
pub fn clear(&mut self) {
self.nodes.clear();
self.root = None;
self.focus = None;
}
}
impl Default for AccessibilityManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct SharedAccessibility {
inner: Arc<RwLock<AccessibilityManager>>,
}
impl SharedAccessibility {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(AccessibilityManager::new())),
}
}
pub fn announce(&self, message: impl Into<String>) {
write_or_recover(&self.inner).announce_polite(message);
}
pub fn announce_now(&self, message: impl Into<String>) {
write_or_recover(&self.inner).announce_assertive(message);
}
pub fn set_focus(&self, id: impl Into<String>) {
write_or_recover(&self.inner).set_focus(id);
}
pub fn focus(&self) -> Option<String> {
read_or_recover(&self.inner).focus().map(|s| s.to_string())
}
}
impl Default for SharedAccessibility {
fn default() -> Self {
Self::new()
}
}
pub fn accessibility_manager() -> AccessibilityManager {
AccessibilityManager::new()
}
pub fn shared_accessibility() -> SharedAccessibility {
SharedAccessibility::new()
}
pub fn announce(message: impl Into<String>) -> Announcement {
Announcement::polite(message)
}
pub fn announce_now(message: impl Into<String>) -> Announcement {
Announcement::assertive(message)
}