use std::io::{self, Write};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Role {
Window,
Panel,
Form,
Label,
TextField,
StaticText,
Button,
CheckBox,
RadioButton,
ComboBox,
List,
ListItem,
Tree,
TreeItem,
PageTab,
PageTabList,
ProgressBar,
Slider,
SpinButton,
Section,
Separator,
Graphic,
Tooltip,
StatusBar,
MenuBar,
Menu,
MenuItem,
ContextMenu,
Table,
TableRow,
TableCell,
DescriptionList,
DescriptionTerm,
DescriptionValue,
Alert,
Landmark,
Generic,
}
impl Role {
pub fn as_str(&self) -> &'static str {
match self {
Role::Window => "window",
Role::Panel => "panel",
Role::Form => "form",
Role::Label => "label",
Role::TextField => "text field",
Role::StaticText => "static",
Role::Button => "push button",
Role::CheckBox => "check box",
Role::RadioButton => "radio button",
Role::ComboBox => "combo box",
Role::List => "list",
Role::ListItem => "list item",
Role::Tree => "tree",
Role::TreeItem => "tree item",
Role::PageTab => "page tab",
Role::PageTabList => "page tab list",
Role::ProgressBar => "progress bar",
Role::Slider => "slider",
Role::SpinButton => "spinbutton",
Role::Section => "section",
Role::Separator => "separator",
Role::Graphic => "graphic",
Role::Tooltip => "tooltip",
Role::StatusBar => "status bar",
Role::MenuBar => "menu bar",
Role::Menu => "menu",
Role::MenuItem => "menu item",
Role::ContextMenu => "popup menu",
Role::Table => "table",
Role::TableRow => "table row",
Role::TableCell => "table cell",
Role::DescriptionList => "description list",
Role::DescriptionTerm => "description term",
Role::DescriptionValue => "description value",
Role::Alert => "alert",
Role::Landmark => "landmark",
Role::Generic => "unknown",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AnnounceLevel {
Polite,
Assertive,
}
impl AnnounceLevel {
fn as_str(&self) -> &'static str {
match self {
AnnounceLevel::Polite => "polite",
AnnounceLevel::Assertive => "assertive",
}
}
}
pub fn announce<W: Write>(writer: &mut W, text: &str, level: AnnounceLevel) -> io::Result<()> {
let escaped = text.replace(['\x07', '\x1b'], "");
write!(writer, "\x1b]99;{};{}\x07", level.as_str(), escaped)
}
pub fn announce_with_meta<W: Write>(
writer: &mut W,
role: Role,
label: Option<&str>,
value: Option<&str>,
description: Option<&str>,
level: AnnounceLevel,
) -> io::Result<()> {
let role_str = role.as_str();
let label_str = label.unwrap_or("");
let value_str = value.unwrap_or("");
let desc_str = description.unwrap_or("");
let params = format!("{},{},{},{}", role_str, label_str, value_str, desc_str);
let escaped = params.replace(['\x07', '\x1b'], "");
write!(writer, "\x1b]99;{};{}\x07", level.as_str(), escaped)
}
#[derive(Debug, Clone)]
pub struct Announcer {
enabled: bool,
}
impl Announcer {
pub fn new() -> Self {
Self { enabled: true }
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn announce<W: Write>(&self, writer: &mut W, text: &str, level: AnnounceLevel) -> io::Result<()> {
if self.enabled {
announce(writer, text, level)
} else {
Ok(())
}
}
pub fn announce_change<W: Write>(
&self,
writer: &mut W,
role: Role,
label: Option<&str>,
description: Option<&str>,
level: AnnounceLevel,
) -> io::Result<()> {
if self.enabled {
announce_with_meta(writer, role, label, None, description, level)
} else {
Ok(())
}
}
}
impl Default for Announcer {
fn default() -> Self {
Self::new()
}
}
pub trait Accessibility {
fn role(&self) -> Role {
Role::Generic
}
fn label(&self) -> Option<&str> {
None
}
fn description(&self) -> Option<&str> {
None
}
fn value(&self) -> Option<&str> {
None
}
fn expanded(&self) -> Option<bool> {
None
}
fn checked(&self) -> Option<bool> {
None
}
fn disabled(&self) -> bool {
false
}
fn has_popup(&self) -> bool {
false
}
fn keyboard_shortcut(&self) -> Option<&str> {
None
}
}
impl Accessibility for () {
fn role(&self) -> Role {
Role::Generic
}
}
#[macro_export]
macro_rules! accessible_widget {
(
role: $role:expr
$(,)?
) => {
fn role(&self) -> Role { $role }
};
(
role: $role:expr
$(, label: $label:expr)?
$(, description: $desc:expr)?
$(, value: $value:expr)?
$(, checked: $checked:expr)?
$(, expanded: $expanded:expr)?
$(, disabled: $disabled:expr)?
$(, has_popup: $popup:expr)?
$(, keyboard_shortcut: $shortcut:expr)?
) => {
fn role(&self) -> Role { $role }
$(fn label(&self) -> Option<&str> { Some($label) })?
$(fn description(&self) -> Option<&str> { Some($desc) })?
$(fn value(&self) -> Option<&str> { Some($value) })?
$(fn checked(&self) -> Option<bool> { Some($checked) })?
$(fn expanded(&self) -> Option<bool> { Some($expanded) })?
$(fn disabled(&self) -> bool { $disabled })?
$(fn has_popup(&self) -> bool { $popup })?
$(fn keyboard_shortcut(&self) -> Option<&str> { Some($shortcut) })?
};
}