use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
use crate::color::ColorScheme;
use crate::radius::BorderRadius;
use crate::spacing::Spacing;
use crate::typography::Typography;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AnimationConfig {
pub enabled: bool,
pub duration_ms: f32,
}
impl Default for AnimationConfig {
fn default() -> Self { Self { enabled: true, duration_ms: 110.0 } }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TitleAlign {
Leading,
Center,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AppBarStyle {
pub title_align: TitleAlign,
pub show_traffic_lights: bool,
pub height: f32,
pub elevation: f32,
}
impl Default for AppBarStyle {
fn default() -> Self {
Self { title_align: TitleAlign::Leading, show_traffic_lights: false, height: 44.0, elevation: 1.0 }
}
}
#[derive(Clone)]
pub struct ThemeData {
pub colors: ColorScheme,
pub animation: AnimationConfig,
pub typography: Typography,
pub spacing: Spacing,
pub radius: BorderRadius,
pub is_dark: bool,
pub app_bar: AppBarStyle,
pub ext: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
}
impl std::fmt::Debug for ThemeData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ThemeData")
.field("colors", &self.colors)
.field("animation", &self.animation)
.field("typography", &self.typography)
.field("spacing", &self.spacing)
.field("radius", &self.radius)
.field("is_dark", &self.is_dark)
.field("app_bar", &self.app_bar)
.field("ext", &format_args!("{} extension(s)", self.ext.len()))
.finish()
}
}
pub trait RosaceTheme: Send + Sync + 'static {
fn theme_data(&self) -> &ThemeData;
}
impl ThemeData {
pub fn animations(mut self, enabled: bool) -> Self {
self.animation.enabled = enabled; self
}
pub fn animation_ms(mut self, ms: f32) -> Self {
self.animation.duration_ms = ms; self
}
pub fn with_ext<T: Any + Send + Sync + 'static>(mut self, ext: T) -> Self {
self.ext.insert(TypeId::of::<T>(), Arc::new(ext));
self
}
pub fn ext<T: Any + Send + Sync + 'static>(&self) -> Option<&T> {
self.ext.get(&TypeId::of::<T>()).and_then(|a| a.downcast_ref::<T>())
}
}
#[cfg(test)]
mod tests {
use crate::built_in::light_theme;
#[derive(Debug, Clone, Copy, PartialEq)]
struct BadgeStyle {
corner_radius: f32,
}
#[test]
fn ext_round_trips_a_custom_style() {
let theme = light_theme().with_ext(BadgeStyle { corner_radius: 6.0 });
let badge = theme.ext::<BadgeStyle>().expect("BadgeStyle should be present");
assert_eq!(badge.corner_radius, 6.0);
}
#[test]
fn ext_is_none_when_never_set() {
let theme = light_theme();
assert!(theme.ext::<BadgeStyle>().is_none());
}
#[test]
fn ext_distinguishes_by_type() {
#[derive(Debug, Clone, Copy, PartialEq)]
struct OtherStyle {
weight: f32,
}
let theme = light_theme().with_ext(BadgeStyle { corner_radius: 6.0 });
assert!(theme.ext::<OtherStyle>().is_none());
assert!(theme.ext::<BadgeStyle>().is_some());
}
}