use crate::{SourceLocation, Srgb};
use serde::{Deserialize, Serialize};
pub const BUILT_IN_MOTION: [(&str, f64, f64); 6] = [
("press", 0.12, 1.0),
("snappy", 0.22, 0.85),
("smooth", 0.45, 1.0),
("bouncy", 0.50, 0.62),
("jelly", 0.42, 0.42),
("hero", 0.48, 0.86),
];
pub const COLOR_ROLES: &[&str] = &[
"surface.canvas",
"surface.panel",
"surface.raised",
"text.primary",
"text.secondary",
"text.placeholder",
"icon.primary",
"icon.secondary",
"accent",
"on_accent",
"status.success",
"status.warning",
"status.error",
"control.border",
"control.track",
"control.thumb",
"focus.indicator",
"selection.background",
"selection.foreground",
"disabled.foreground",
"disabled.surface",
"fill.subtle",
"fill.strong",
];
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct StyleSheet {
pub motion: Vec<MotionToken>,
pub rules: Vec<StyleRule>,
#[serde(default)]
pub transitions: Vec<TransitionDef>,
#[serde(default)]
pub patterns: Vec<PatternDef>,
#[serde(default)]
pub accents: Vec<AccentDef>,
#[serde(default)]
pub text_weights: Vec<TextWeight>,
#[serde(default)]
pub corners: Vec<CornerDef>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct CornerDef {
pub name: String,
pub radius: f64,
#[serde(default)]
pub targets: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TextWeight {
pub style: String,
pub weight: String,
#[serde(default)]
pub size: Option<f64>,
#[serde(default)]
pub tv_size: Option<f64>,
}
pub const TEXT_WEIGHTS: &[&str] = &[
"ultralight",
"thin",
"light",
"regular",
"medium",
"semibold",
"bold",
"heavy",
"black",
];
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AccentDef {
pub name: String,
pub color: ColorRef,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct PatternDef {
pub name: String,
pub bands: Vec<Band>,
pub twill: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Band {
pub color: Option<ColorRef>,
pub width: f64,
pub opacity: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct TransitionDef {
pub name: String,
pub kind: TransitionKind,
pub motion: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum TransitionKind {
Fade,
Pop { from: f64 },
Grow { edge: Edge },
Slide { edge: Edge, distance: f64 },
Move { edge: Edge },
}
pub fn built_in_transitions() -> Vec<TransitionDef> {
vec![
TransitionDef {
name: "fade".into(),
kind: TransitionKind::Fade,
motion: "smooth".into(),
},
TransitionDef {
name: "pop".into(),
kind: TransitionKind::Pop { from: 0.8 },
motion: "bouncy".into(),
},
TransitionDef {
name: "slide".into(),
kind: TransitionKind::Slide {
edge: Edge::Bottom,
distance: 16.0,
},
motion: "smooth".into(),
},
]
}
impl StyleSheet {
pub fn token(&self, name: &str) -> Option<&MotionToken> {
self.motion.iter().find(|token| token.name == name)
}
pub fn accent(&self, name: &str) -> Option<&AccentDef> {
let wanted = name.replace('_', "").to_lowercase();
self.accents
.iter()
.find(|accent| accent.name.replace('_', "").to_lowercase() == wanted)
}
pub fn pattern(&self, name: &str) -> Option<&PatternDef> {
self.patterns.iter().find(|pattern| pattern.name == name)
}
pub fn transition(&self, name: &str) -> Option<&TransitionDef> {
self.transitions
.iter()
.find(|transition| transition.name == name)
}
pub fn corner(&self, name: &str, platform: &str) -> Option<f64> {
self.corners
.iter()
.rev()
.find(|corner| {
corner.name == name
&& (corner.targets.is_empty() || corner.targets.iter().any(|t| t == platform))
})
.map(|corner| corner.radius)
}
pub fn rules_for<'a>(&'a self, platform: &'a str) -> impl Iterator<Item = &'a StyleRule> {
self.rules.iter().filter(move |rule| {
rule.targets.is_empty() || rule.targets.iter().any(|t| t == platform)
})
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MotionToken {
pub name: String,
pub period: f64,
pub damping: f64,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StyleRule {
pub component: String,
pub variant: Option<String>,
pub targets: Vec<String>,
pub exempt: Vec<String>,
pub base: Vec<StyleEffect>,
pub states: Vec<StateStyle>,
pub settle: Option<String>,
pub source: SourceLocation,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StateStyle {
pub state: StyleState,
pub effects: Vec<StyleEffect>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StyleState {
Hover,
Pressed,
Selected,
Focus,
Disabled,
}
impl StyleState {
pub const ALL: [StyleState; 5] = [
StyleState::Hover,
StyleState::Pressed,
StyleState::Selected,
StyleState::Focus,
StyleState::Disabled,
];
pub fn name(self) -> &'static str {
match self {
StyleState::Hover => "hover",
StyleState::Pressed => "pressed",
StyleState::Selected => "selected",
StyleState::Focus => "focus",
StyleState::Disabled => "disabled",
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum StyleEffect {
Background {
color: ColorRef,
},
Foreground {
color: ColorRef,
},
Border {
color: ColorRef,
width: f64,
},
Corner {
radius: f64,
},
Padding {
length: f64,
},
Size {
size: ControlSize,
},
Fill {
color: ColorRef,
},
Indicator {
color: ColorRef,
edge: Edge,
width: f64,
},
Scale {
x: f64,
y: f64,
motion: String,
},
Offset {
x: f64,
y: f64,
motion: String,
},
Opacity {
value: f64,
motion: String,
},
Shadow {
radius: f64,
y: f64,
opacity: f64,
},
System,
Nothing,
Metric {
metric: Metric,
value: Option<f64>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Metric {
Gap,
PaddingX,
PaddingY,
Width,
Height,
FontSize,
RowHeight,
}
impl Metric {
pub const ALL: [Metric; 7] = [
Metric::Gap,
Metric::PaddingX,
Metric::PaddingY,
Metric::Width,
Metric::Height,
Metric::FontSize,
Metric::RowHeight,
];
pub fn name(self) -> &'static str {
match self {
Metric::Gap => "gap",
Metric::PaddingX => "padding_x",
Metric::PaddingY => "padding_y",
Metric::Width => "width",
Metric::Height => "height",
Metric::FontSize => "font_size",
Metric::RowHeight => "row_height",
}
}
}
impl StyleEffect {
pub fn name(&self) -> &'static str {
match self {
StyleEffect::Background { .. } => "background",
StyleEffect::Foreground { .. } => "foreground",
StyleEffect::Border { .. } => "border",
StyleEffect::Corner { .. } => "corner",
StyleEffect::Padding { .. } => "padding",
StyleEffect::Size { .. } => "size",
StyleEffect::Fill { .. } => "fill",
StyleEffect::Indicator { .. } => "indicator",
StyleEffect::Scale { .. } => "scale",
StyleEffect::Offset { .. } => "offset",
StyleEffect::Opacity { .. } => "opacity",
StyleEffect::Shadow { .. } => "shadow",
StyleEffect::System => "system",
StyleEffect::Nothing => "none",
StyleEffect::Metric { metric, .. } => metric.name(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ColorRef {
Role { role: String },
Fixed { light: Srgb, dark: Srgb },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ControlSize {
Small,
Regular,
Large,
ExtraLarge,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Edge {
Leading,
Trailing,
Top,
Bottom,
}