use crate::color::Color;
use crate::id::Id;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum AccessibilityRole {
#[default]
None,
Button,
Link,
Heading {
level: u8,
},
Label,
StaticText,
TextInput,
TextArea,
Checkbox,
RadioButton,
Slider,
Group,
List,
ListItem,
Menu,
MenuItem,
MenuBar,
Tab,
TabList,
TabPanel,
Dialog,
AlertDialog,
Toolbar,
Image,
ProgressBar,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum LiveRegionMode {
#[default]
Off,
Polite,
Assertive,
}
#[derive(Debug, Clone, Default)]
pub struct AccessibilityConfig {
pub focusable: bool,
pub role: AccessibilityRole,
pub label: String,
pub description: String,
pub value: String,
pub value_min: Option<f32>,
pub value_max: Option<f32>,
pub checked: Option<bool>,
pub tab_index: Option<i32>,
pub focus_right: Option<u32>,
pub focus_left: Option<u32>,
pub focus_up: Option<u32>,
pub focus_down: Option<u32>,
pub show_ring: bool,
pub ring_color: Option<Color>,
pub ring_width: Option<u16>,
pub live_region: LiveRegionMode,
}
impl AccessibilityConfig {
pub fn new() -> Self {
Self {
show_ring: true,
..Default::default()
}
}
}
pub struct AccessibilityBuilder {
pub(crate) config: AccessibilityConfig,
}
impl AccessibilityBuilder {
pub(crate) fn new() -> Self {
Self {
config: AccessibilityConfig::new(),
}
}
pub fn focusable(&mut self) -> &mut Self {
self.config.focusable = true;
self
}
pub fn button(&mut self, label: &str) -> &mut Self {
self.config.role = AccessibilityRole::Button;
self.config.label = label.to_string();
self.config.focusable = true;
self
}
pub fn heading(&mut self, label: &str, level: u8) -> &mut Self {
self.config.role = AccessibilityRole::Heading { level };
self.config.label = label.to_string();
self
}
pub fn link(&mut self, label: &str) -> &mut Self {
self.config.role = AccessibilityRole::Link;
self.config.label = label.to_string();
self.config.focusable = true;
self
}
pub fn static_text(&mut self, label: &str) -> &mut Self {
self.config.role = AccessibilityRole::StaticText;
self.config.label = label.to_string();
self
}
pub fn checkbox(&mut self, label: &str) -> &mut Self {
self.config.role = AccessibilityRole::Checkbox;
self.config.label = label.to_string();
self.config.focusable = true;
self
}
pub fn slider(&mut self, label: &str) -> &mut Self {
self.config.role = AccessibilityRole::Slider;
self.config.label = label.to_string();
self.config.focusable = true;
self
}
pub fn image(&mut self, alt: &str) -> &mut Self {
self.config.role = AccessibilityRole::Image;
self.config.label = alt.to_string();
self
}
pub fn role(&mut self, role: AccessibilityRole) -> &mut Self {
self.config.role = role;
self
}
pub fn label(&mut self, label: &str) -> &mut Self {
self.config.label = label.to_string();
self
}
pub fn description(&mut self, desc: &str) -> &mut Self {
self.config.description = desc.to_string();
self
}
pub fn value(&mut self, value: &str) -> &mut Self {
self.config.value = value.to_string();
self
}
pub fn value_min(&mut self, min: f32) -> &mut Self {
self.config.value_min = Some(min);
self
}
pub fn value_max(&mut self, max: f32) -> &mut Self {
self.config.value_max = Some(max);
self
}
pub fn checked(&mut self, checked: bool) -> &mut Self {
self.config.checked = Some(checked);
self
}
pub fn tab_index(&mut self, index: i32) -> &mut Self {
self.config.tab_index = Some(index);
self
}
pub fn focus_right(&mut self, target: impl Into<Id>) -> &mut Self {
self.config.focus_right = Some(target.into().id);
self
}
pub fn focus_left(&mut self, target: impl Into<Id>) -> &mut Self {
self.config.focus_left = Some(target.into().id);
self
}
pub fn focus_up(&mut self, target: impl Into<Id>) -> &mut Self {
self.config.focus_up = Some(target.into().id);
self
}
pub fn focus_down(&mut self, target: impl Into<Id>) -> &mut Self {
self.config.focus_down = Some(target.into().id);
self
}
pub fn disable_ring(&mut self) -> &mut Self {
self.config.show_ring = false;
self
}
pub fn ring_color(&mut self, color: impl Into<Color>) -> &mut Self {
self.config.ring_color = Some(color.into());
self
}
pub fn ring_width(&mut self, width: u16) -> &mut Self {
self.config.ring_width = Some(width);
self
}
pub fn live_region_polite(&mut self) -> &mut Self {
self.config.live_region = LiveRegionMode::Polite;
self
}
pub fn live_region_assertive(&mut self) -> &mut Self {
self.config.live_region = LiveRegionMode::Assertive;
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_button_sets_role_and_focusable() {
let mut builder = AccessibilityBuilder::new();
builder.button("Submit");
assert_eq!(builder.config.role, AccessibilityRole::Button);
assert_eq!(builder.config.label, "Submit");
assert!(builder.config.focusable);
assert!(builder.config.show_ring); }
#[test]
fn builder_heading_sets_level() {
let mut builder = AccessibilityBuilder::new();
builder.heading("Settings", 2);
assert_eq!(
builder.config.role,
AccessibilityRole::Heading { level: 2 }
);
assert_eq!(builder.config.label, "Settings");
}
#[test]
fn builder_disable_ring() {
let mut builder = AccessibilityBuilder::new();
builder.focusable().disable_ring();
assert!(builder.config.focusable);
assert!(!builder.config.show_ring);
}
#[test]
fn builder_focus_directions() {
let mut builder = AccessibilityBuilder::new();
builder
.focusable()
.focus_right(("next", 0u32))
.focus_left(("prev", 0u32))
.focus_up(("above", 0u32))
.focus_down(("below", 0u32));
assert_eq!(builder.config.focus_right, Some(Id::from(("next", 0u32)).id));
assert_eq!(builder.config.focus_left, Some(Id::from(("prev", 0u32)).id));
assert_eq!(builder.config.focus_up, Some(Id::from(("above", 0u32)).id));
assert_eq!(builder.config.focus_down, Some(Id::from(("below", 0u32)).id));
}
#[test]
fn builder_slider_properties() {
let mut builder = AccessibilityBuilder::new();
builder
.role(AccessibilityRole::Slider)
.label("Volume")
.description("Adjusts the master volume from 0 to 100")
.value("75")
.value_min(0.0)
.value_max(100.0);
assert_eq!(builder.config.role, AccessibilityRole::Slider);
assert_eq!(builder.config.label, "Volume");
assert_eq!(builder.config.value, "75");
assert_eq!(builder.config.value_min, Some(0.0));
assert_eq!(builder.config.value_max, Some(100.0));
}
#[test]
fn builder_ring_styling() {
let mut builder = AccessibilityBuilder::new();
builder
.focusable()
.ring_color(Color::rgb(0.0, 120.0, 255.0))
.ring_width(3);
assert!(builder.config.show_ring);
assert_eq!(builder.config.ring_color, Some(Color::rgb(0.0, 120.0, 255.0)));
assert_eq!(builder.config.ring_width, Some(3));
}
#[test]
fn ring_color_accepts_into() {
let mut builder = AccessibilityBuilder::new();
builder.ring_color((0u8, 120u8, 255u8));
assert_eq!(builder.config.ring_color, Some(Color::rgb(0.0, 120.0, 255.0)));
}
#[test]
fn ring_defaults_are_none() {
let config = AccessibilityConfig::new();
assert!(config.show_ring);
assert!(config.ring_color.is_none());
assert!(config.ring_width.is_none());
}
}