use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContrastMode {
#[default]
Normal,
High,
}
impl ContrastMode {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Normal => "Normal",
Self::High => "High Contrast",
}
}
#[must_use]
pub const fn toggle(self) -> Self {
match self {
Self::Normal => Self::High,
Self::High => Self::Normal,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum MotionPreference {
#[default]
Full,
Reduced,
}
impl MotionPreference {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Full => "Full Motion",
Self::Reduced => "Reduced Motion",
}
}
#[must_use]
pub const fn toggle(self) -> Self {
match self {
Self::Full => Self::Reduced,
Self::Reduced => Self::Full,
}
}
#[must_use]
pub const fn animations_enabled(self) -> bool {
matches!(self, Self::Full)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum FocusVisibility {
#[default]
Normal,
Enhanced,
}
impl FocusVisibility {
#[must_use]
pub const fn label(self) -> &'static str {
match self {
Self::Normal => "Normal",
Self::Enhanced => "Enhanced",
}
}
#[must_use]
pub const fn toggle(self) -> Self {
match self {
Self::Normal => Self::Enhanced,
Self::Enhanced => Self::Normal,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DisplayPreferences {
pub contrast: ContrastMode,
pub motion: MotionPreference,
pub focus_visibility: FocusVisibility,
pub show_shortcut_hints: bool,
}
impl Default for DisplayPreferences {
fn default() -> Self {
Self {
contrast: ContrastMode::default(),
motion: MotionPreference::default(),
focus_visibility: FocusVisibility::default(),
show_shortcut_hints: true,
}
}
}
impl DisplayPreferences {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub const fn toggle_contrast(&mut self) {
self.contrast = self.contrast.toggle();
}
pub const fn toggle_motion(&mut self) {
self.motion = self.motion.toggle();
}
pub const fn toggle_focus_visibility(&mut self) {
self.focus_visibility = self.focus_visibility.toggle();
}
pub const fn toggle_shortcut_hints(&mut self) {
self.show_shortcut_hints = !self.show_shortcut_hints;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn contrast_mode_toggle() {
let mode = ContrastMode::Normal;
assert_eq!(mode.toggle(), ContrastMode::High);
assert_eq!(mode.toggle().toggle(), ContrastMode::Normal);
}
#[test]
fn contrast_mode_labels() {
assert_eq!(ContrastMode::Normal.label(), "Normal");
assert_eq!(ContrastMode::High.label(), "High Contrast");
}
#[test]
fn motion_preference_toggle() {
let pref = MotionPreference::Full;
assert!(pref.animations_enabled());
let reduced = pref.toggle();
assert_eq!(reduced, MotionPreference::Reduced);
assert!(!reduced.animations_enabled());
}
#[test]
fn focus_visibility_toggle() {
let vis = FocusVisibility::Normal;
assert_eq!(vis.toggle(), FocusVisibility::Enhanced);
}
#[test]
fn display_preferences_default() {
let prefs = DisplayPreferences::new();
assert_eq!(prefs.contrast, ContrastMode::Normal);
assert_eq!(prefs.motion, MotionPreference::Full);
assert_eq!(prefs.focus_visibility, FocusVisibility::Normal);
assert!(prefs.show_shortcut_hints);
}
#[test]
fn display_preferences_toggles() {
let mut prefs = DisplayPreferences::new();
prefs.toggle_contrast();
assert_eq!(prefs.contrast, ContrastMode::High);
prefs.toggle_motion();
assert_eq!(prefs.motion, MotionPreference::Reduced);
prefs.toggle_focus_visibility();
assert_eq!(prefs.focus_visibility, FocusVisibility::Enhanced);
prefs.toggle_shortcut_hints();
assert!(!prefs.show_shortcut_hints);
}
#[test]
fn display_preferences_serde_roundtrip() {
let mut prefs = DisplayPreferences::new();
prefs.toggle_contrast();
prefs.toggle_motion();
let json = serde_json::to_string(&prefs).unwrap();
let decoded: DisplayPreferences = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, prefs);
}
#[test]
fn contrast_mode_serde_roundtrip() {
for mode in [ContrastMode::Normal, ContrastMode::High] {
let json = serde_json::to_string(&mode).unwrap();
let decoded: ContrastMode = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, mode);
}
}
#[test]
fn motion_preference_serde_roundtrip() {
for pref in [MotionPreference::Full, MotionPreference::Reduced] {
let json = serde_json::to_string(&pref).unwrap();
let decoded: MotionPreference = serde_json::from_str(&json).unwrap();
assert_eq!(decoded, pref);
}
}
#[test]
fn motion_preference_labels() {
assert_eq!(MotionPreference::Full.label(), "Full Motion");
assert_eq!(MotionPreference::Reduced.label(), "Reduced Motion");
}
#[test]
fn focus_visibility_labels() {
assert_eq!(FocusVisibility::Normal.label(), "Normal");
assert_eq!(FocusVisibility::Enhanced.label(), "Enhanced");
}
}