use std::cell::{Cell, RefCell};
use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf};
use cranpose_macros::composable;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct AccessibilityOptions {
pub font_scale: f32,
pub reduce_motion: bool,
pub reduce_transparency: bool,
pub increase_contrast: bool,
pub bold_text: bool,
pub invert_colors: bool,
}
impl Default for AccessibilityOptions {
fn default() -> Self {
Self {
font_scale: 1.0,
reduce_motion: false,
reduce_transparency: false,
increase_contrast: false,
bold_text: false,
invert_colors: false,
}
}
}
impl AccessibilityOptions {
pub fn normalized(mut self) -> Self {
if !(self.font_scale.is_finite() && self.font_scale > 0.0) {
self.font_scale = 1.0;
}
self
}
}
thread_local! {
static PLATFORM_ACCESSIBILITY_OPTIONS: Cell<AccessibilityOptions> =
const { Cell::new(AccessibilityOptions {
font_scale: 1.0,
reduce_motion: false,
reduce_transparency: false,
increase_contrast: false,
bold_text: false,
invert_colors: false,
}) };
}
pub fn set_platform_accessibility_options(options: AccessibilityOptions) -> bool {
let options = options.normalized();
PLATFORM_ACCESSIBILITY_OPTIONS.with(|cell| {
let changed = cell.get() != options;
cell.set(options);
changed
})
}
pub fn platform_accessibility_options() -> AccessibilityOptions {
PLATFORM_ACCESSIBILITY_OPTIONS.with(Cell::get)
}
pub fn local_accessibility_options() -> CompositionLocal<AccessibilityOptions> {
thread_local! {
static LOCAL: RefCell<Option<CompositionLocal<AccessibilityOptions>>> =
const { RefCell::new(None) };
}
LOCAL.with(|cell| {
let mut local = cell.borrow_mut();
local
.get_or_insert_with(|| compositionLocalOf(platform_accessibility_options))
.clone()
})
}
#[composable]
pub fn ProvideAccessibilityOptions(options: AccessibilityOptions, content: impl FnOnce()) {
let provided = local_accessibility_options().provides(options.normalized());
CompositionLocalProvider(vec![provided], move || content());
}
#[cfg(test)]
mod tests {
use std::{cell::Cell, rc::Rc};
use super::*;
use crate::captured_in_composition;
fn large_and_still() -> AccessibilityOptions {
AccessibilityOptions {
font_scale: 1.5,
reduce_motion: true,
..AccessibilityOptions::default()
}
}
#[test]
fn the_platform_options_report_a_change_once_and_mend_a_bad_scale() {
set_platform_accessibility_options(AccessibilityOptions::default());
assert!(set_platform_accessibility_options(large_and_still()));
assert!(!set_platform_accessibility_options(large_and_still()));
assert_eq!(platform_accessibility_options(), large_and_still());
let bad_scale = AccessibilityOptions {
font_scale: 0.0,
..AccessibilityOptions::default()
};
assert!(set_platform_accessibility_options(bad_scale));
assert_eq!(platform_accessibility_options().font_scale, 1.0);
}
#[test]
fn a_composable_reads_the_platform_options_and_a_provider_wins_over_them() {
set_platform_accessibility_options(large_and_still());
let seen = captured_in_composition(|| local_accessibility_options().current());
set_platform_accessibility_options(AccessibilityOptions::default());
assert_eq!(seen, Some(large_and_still()));
let under_provider = captured_in_composition(|| {
let slot = Rc::new(Cell::new(None));
let sink = Rc::clone(&slot);
let local = local_accessibility_options();
ProvideAccessibilityOptions(large_and_still(), move || sink.set(Some(local.current())));
slot.get()
});
assert_eq!(under_provider, Some(Some(large_and_still())));
}
}