use nami::{Computed, impl_constant, signal::IntoComputed};
use suiteki::Str;
use crate::metadata::MetadataKey;
use crate::{AnyView, Environment, IgnorableMetadata, View};
#[derive(Debug, Clone)]
pub struct AccessibilityLabel(Computed<Str>);
impl MetadataKey for AccessibilityLabel {}
impl AccessibilityLabel {
pub fn new(label: impl IntoComputed<Str>) -> Self {
Self(label.into_computed())
}
#[must_use]
pub const fn signal(&self) -> &Computed<Str> {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct AccessibilityValue(Computed<Str>);
impl MetadataKey for AccessibilityValue {}
impl AccessibilityValue {
pub fn new(value: impl IntoComputed<Str>) -> Self {
Self(value.into_computed())
}
#[must_use]
pub const fn signal(&self) -> &Computed<Str> {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccessibilityIdentifier(Str);
impl MetadataKey for AccessibilityIdentifier {}
impl AccessibilityIdentifier {
pub fn new(identifier: impl Into<Str>) -> Self {
Self(identifier.into())
}
#[must_use]
pub const fn as_str(&self) -> &Str {
&self.0
}
#[must_use]
pub fn into_str(self) -> Str {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AccessibilityRole {
Button,
Link,
Image,
Text,
Header,
Footer,
Navigation,
Main,
Search,
Article,
Section,
List,
ListItem,
Checkbox,
RadioButton,
Switch,
Slider,
ProgressBar,
Tab,
TabList,
TabPanel,
Menu,
MenuItem,
MenuBar,
MenuItemCheckbox,
MenuItemRadio,
Combobox,
Option,
Group,
Dialog,
}
impl MetadataKey for AccessibilityRole {}
pub fn default_role(env: &Environment, view: impl View, role: AccessibilityRole) -> AnyView {
if env.get::<AccessibilityRole>().is_some() {
AnyView::new(view)
} else {
AnyView::new(IgnorableMetadata::new(view, role))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AccessibilityHidden(bool);
impl MetadataKey for AccessibilityHidden {}
impl AccessibilityHidden {
#[must_use]
pub const fn new(hidden: bool) -> Self {
Self(hidden)
}
#[must_use]
pub const fn is_hidden(&self) -> bool {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum AccessibilityChildren {
#[default]
Automatic,
ExcludeDescendants,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessibilityChecked {
False,
True,
Mixed,
}
impl MetadataKey for AccessibilityChildren {}
impl AccessibilityChildren {
#[must_use]
pub const fn excludes_descendants(&self) -> bool {
matches!(self, Self::ExcludeDescendants)
}
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AccessibilityState {
disabled: bool,
selected: bool,
checked: Option<AccessibilityChecked>,
expanded: Option<bool>,
busy: bool,
hidden: bool,
}
impl MetadataKey for AccessibilityState {}
impl_constant!(AccessibilityState);
impl AccessibilityState {
#[must_use]
pub const fn new() -> Self {
Self {
disabled: false,
selected: false,
checked: None,
expanded: None,
busy: false,
hidden: false,
}
}
#[must_use]
pub const fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
#[must_use]
pub const fn selected(mut self, selected: bool) -> Self {
self.selected = selected;
self
}
#[must_use]
pub const fn checked(mut self, checked: Option<bool>) -> Self {
self.checked = match checked {
Some(true) => Some(AccessibilityChecked::True),
Some(false) => Some(AccessibilityChecked::False),
None => None,
};
self
}
#[must_use]
pub const fn mixed(mut self) -> Self {
self.checked = Some(AccessibilityChecked::Mixed);
self
}
#[must_use]
pub const fn expanded(mut self, expanded: Option<bool>) -> Self {
self.expanded = expanded;
self
}
#[must_use]
pub const fn busy(mut self, busy: bool) -> Self {
self.busy = busy;
self
}
#[must_use]
pub const fn hidden(mut self, hidden: bool) -> Self {
self.hidden = hidden;
self
}
#[must_use]
pub const fn is_disabled(&self) -> bool {
self.disabled
}
#[must_use]
pub const fn is_selected(&self) -> bool {
self.selected
}
#[must_use]
pub const fn checked_state(&self) -> Option<AccessibilityChecked> {
self.checked
}
#[must_use]
pub const fn expanded_state(&self) -> Option<bool> {
self.expanded
}
#[must_use]
pub const fn is_busy(&self) -> bool {
self.busy
}
#[must_use]
pub const fn is_hidden(&self) -> bool {
self.hidden
}
}
#[derive(Debug, Clone)]
pub struct AccessibilityStateSignal(Computed<AccessibilityState>);
impl MetadataKey for AccessibilityStateSignal {}
impl AccessibilityStateSignal {
#[must_use]
pub fn new(state: impl IntoComputed<AccessibilityState>) -> Self {
Self(state.into_computed())
}
#[must_use]
pub const fn state(&self) -> &Computed<AccessibilityState> {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::{AccessibilityRole, default_role};
use crate::{Environment, IgnorableMetadata};
#[test]
fn an_undescribed_surface_is_given_the_default_role() {
let view = default_role(&Environment::new(), (), AccessibilityRole::Group);
let wrapper = view
.downcast_ref::<IgnorableMetadata<AccessibilityRole>>()
.expect("the default role is attached as ignorable metadata");
assert_eq!(wrapper.value, AccessibilityRole::Group);
}
#[test]
fn an_application_supplied_role_passes_the_view_through_untouched() {
let mut env = Environment::new();
env.insert(AccessibilityRole::Image);
let view = default_role(&env, (), AccessibilityRole::Group);
assert!(
view.downcast_ref::<IgnorableMetadata<AccessibilityRole>>()
.is_none(),
"a view whose role the application chose must not be wrapped again"
);
}
}