use crate::core::Color;
use crate::style::WidgetStyle;
#[cfg(device_profile)]
pub fn resolved_theme_style(widget_name: &str) -> Option<WidgetStyle> {
crate::theme::resolved_theme_style(widget_name)
}
#[cfg(not(device_profile))]
pub fn resolved_theme_style(_widget_name: &str) -> Option<WidgetStyle> {
None
}
#[cfg(device_profile)]
pub fn resolved_theme_style_for(kind_name: &str, class_name: Option<&str>) -> Option<WidgetStyle> {
crate::theme::resolved_theme_style_for(kind_name, class_name)
}
#[cfg(not(device_profile))]
pub fn resolved_theme_style_for(
_kind_name: &str,
_class_name: Option<&str>,
) -> Option<WidgetStyle> {
None
}
#[cfg(device_profile)]
pub use crate::theme::{AppearanceMode, Colors, SemanticColor, Theme};
#[cfg(not(device_profile))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SemanticColor {
Info,
Success,
Warning,
Error,
}
#[cfg(not(device_profile))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum AppearanceMode {
#[default]
Light,
Dark,
}
#[cfg(not(device_profile))]
#[derive(Debug, Clone, Copy, Default)]
pub struct Theme {
pub colors: Colors,
}
#[cfg(not(device_profile))]
#[derive(Debug, Clone, Copy, Default)]
pub struct Colors {
pub background: Color,
pub foreground: Color,
pub primary: Color,
pub secondary: Color,
pub accent: Color,
pub error: Color,
pub warning: Color,
pub success: Color,
pub disabled: Color,
pub info: Color,
}
#[cfg(device_profile)]
pub fn semantic_color(token: SemanticColor) -> Option<Color> {
crate::theme::semantic_color(token)
}
#[cfg(not(device_profile))]
pub fn semantic_color(_token: SemanticColor) -> Option<Color> {
None
}
#[cfg(device_profile)]
pub fn theme_manager() -> crate::compat::MutexGuard<'static, crate::theme::ThemeManager> {
crate::theme::global_theme_manager()
}
#[cfg(not(device_profile))]
pub fn theme_manager() -> ThemeManagerPlaceholder {
ThemeManagerPlaceholder
}
#[cfg(not(device_profile))]
#[derive(Debug, Clone, Copy, Default)]
pub struct ThemeManagerPlaceholder;
#[cfg(not(device_profile))]
impl ThemeManagerPlaceholder {
pub fn current_theme(&self) -> Option<Theme> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum HighContrastMode {
#[default]
None,
BlackOnWhite,
WhiteOnBlack,
Custom {
fg: Color,
bg: Color,
},
}
impl HighContrastMode {
pub fn forced_pair(self) -> Option<(Color, Color)> {
match self {
Self::None => None,
Self::BlackOnWhite => Some((Color::WHITE, Color::BLACK)),
Self::WhiteOnBlack => Some((Color::BLACK, Color::WHITE)),
Self::Custom { fg, bg } => Some((bg, fg)),
}
}
pub fn contrast_ratio(self) -> Option<f32> {
self.forced_pair().map(|(bg, fg)| bg.contrast_ratio(fg))
}
pub fn is_active(self) -> bool {
self != Self::None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn none_forces_no_pair() {
assert_eq!(HighContrastMode::None.forced_pair(), None);
assert!(!HighContrastMode::None.is_active());
assert_eq!(HighContrastMode::None.contrast_ratio(), None);
}
#[test]
fn the_named_modes_force_the_documented_pairs() {
assert_eq!(
HighContrastMode::BlackOnWhite.forced_pair(),
Some((Color::WHITE, Color::BLACK))
);
assert_eq!(
HighContrastMode::WhiteOnBlack.forced_pair(),
Some((Color::BLACK, Color::WHITE))
);
assert_eq!(
HighContrastMode::Custom { fg: Color::RED, bg: Color::BLUE }.forced_pair(),
Some((Color::BLUE, Color::RED))
);
}
#[test]
fn the_named_modes_meet_wcag_aaa() {
let ratio = HighContrastMode::BlackOnWhite.contrast_ratio().expect("a forced pair");
assert!((ratio - 21.0).abs() < 1e-3, "expected 21:1, got {ratio}");
assert_eq!(HighContrastMode::WhiteOnBlack.contrast_ratio(), Some(ratio));
}
#[test]
fn a_custom_pair_reports_its_actual_ratio() {
let poor = HighContrastMode::Custom {
fg: Color::rgb(128, 128, 128),
bg: Color::rgb(120, 120, 120),
};
let ratio = poor.contrast_ratio().expect("a forced pair");
assert!(ratio < 1.5, "expected a poor ratio, got {ratio}");
let good = HighContrastMode::Custom { fg: Color::BLACK, bg: Color::WHITE };
assert!(good.contrast_ratio().expect("a forced pair") >= 4.5, "WCAG AA for normal text");
}
#[test]
fn is_active_distinguishes_none_from_the_rest() {
assert!(!HighContrastMode::None.is_active());
assert!(HighContrastMode::BlackOnWhite.is_active());
assert!(HighContrastMode::WhiteOnBlack.is_active());
assert!(HighContrastMode::Custom { fg: Color::BLACK, bg: Color::WHITE }.is_active());
}
}