1use crate::{SourceLocation, Srgb};
10use serde::{Deserialize, Serialize};
11
12pub const BUILT_IN_MOTION: [(&str, f64, f64); 6] = [
15 ("press", 0.12, 1.0),
16 ("snappy", 0.22, 0.85),
17 ("smooth", 0.45, 1.0),
18 ("bouncy", 0.50, 0.62),
19 ("jelly", 0.42, 0.42),
20 ("hero", 0.48, 0.86),
21];
22
23pub const COLOR_ROLES: &[&str] = &[
26 "surface.canvas",
27 "surface.panel",
28 "surface.raised",
29 "text.primary",
30 "text.secondary",
31 "text.placeholder",
32 "icon.primary",
33 "icon.secondary",
34 "accent",
35 "on_accent",
36 "status.success",
37 "status.warning",
38 "status.error",
39 "control.border",
40 "control.track",
41 "control.thumb",
42 "focus.indicator",
43 "selection.background",
44 "selection.foreground",
45 "disabled.foreground",
46 "disabled.surface",
47 "fill.subtle",
48 "fill.strong",
49];
50
51#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
52pub struct StyleSheet {
53 pub motion: Vec<MotionToken>,
55 pub rules: Vec<StyleRule>,
56 #[serde(default)]
58 pub transitions: Vec<TransitionDef>,
59 #[serde(default)]
61 pub patterns: Vec<PatternDef>,
62 #[serde(default)]
65 pub accents: Vec<AccentDef>,
66 #[serde(default)]
69 pub text_weights: Vec<TextWeight>,
70 #[serde(default)]
73 pub corners: Vec<CornerDef>,
74}
75
76#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
79pub struct CornerDef {
80 pub name: String,
81 pub radius: f64,
82 #[serde(default)]
83 pub targets: Vec<String>,
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
89pub struct TextWeight {
90 pub style: String,
92 pub weight: String,
94 #[serde(default)]
97 pub size: Option<f64>,
98 #[serde(default)]
101 pub tv_size: Option<f64>,
102}
103
104pub const TEXT_WEIGHTS: &[&str] = &[
106 "ultralight",
107 "thin",
108 "light",
109 "regular",
110 "medium",
111 "semibold",
112 "bold",
113 "heavy",
114 "black",
115];
116
117#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
119pub struct AccentDef {
120 pub name: String,
121 pub color: ColorRef,
122}
123
124#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
127pub struct PatternDef {
128 pub name: String,
129 pub bands: Vec<Band>,
130 pub twill: bool,
131}
132
133#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
136pub struct Band {
137 pub color: Option<ColorRef>,
138 pub width: f64,
139 pub opacity: f64,
140}
141
142#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
144pub struct TransitionDef {
145 pub name: String,
146 pub kind: TransitionKind,
147 pub motion: String,
148}
149
150#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
151#[serde(tag = "kind", rename_all = "snake_case")]
152pub enum TransitionKind {
153 Fade,
155 Pop { from: f64 },
157 Grow { edge: Edge },
159 Slide { edge: Edge, distance: f64 },
161 Move { edge: Edge },
164}
165
166pub fn built_in_transitions() -> Vec<TransitionDef> {
168 vec![
169 TransitionDef {
170 name: "fade".into(),
171 kind: TransitionKind::Fade,
172 motion: "smooth".into(),
173 },
174 TransitionDef {
175 name: "pop".into(),
176 kind: TransitionKind::Pop { from: 0.8 },
177 motion: "bouncy".into(),
178 },
179 TransitionDef {
180 name: "slide".into(),
181 kind: TransitionKind::Slide {
182 edge: Edge::Bottom,
183 distance: 16.0,
184 },
185 motion: "smooth".into(),
186 },
187 ]
188}
189
190impl StyleSheet {
191 pub fn token(&self, name: &str) -> Option<&MotionToken> {
192 self.motion.iter().find(|token| token.name == name)
193 }
194
195 pub fn accent(&self, name: &str) -> Option<&AccentDef> {
198 let wanted = name.replace('_', "").to_lowercase();
199 self.accents
200 .iter()
201 .find(|accent| accent.name.replace('_', "").to_lowercase() == wanted)
202 }
203
204 pub fn pattern(&self, name: &str) -> Option<&PatternDef> {
205 self.patterns.iter().find(|pattern| pattern.name == name)
206 }
207
208 pub fn transition(&self, name: &str) -> Option<&TransitionDef> {
209 self.transitions
210 .iter()
211 .find(|transition| transition.name == name)
212 }
213
214 pub fn corner(&self, name: &str, platform: &str) -> Option<f64> {
217 self.corners
218 .iter()
219 .rev()
220 .find(|corner| {
221 corner.name == name
222 && (corner.targets.is_empty() || corner.targets.iter().any(|t| t == platform))
223 })
224 .map(|corner| corner.radius)
225 }
226
227 pub fn rules_for<'a>(&'a self, platform: &'a str) -> impl Iterator<Item = &'a StyleRule> {
230 self.rules.iter().filter(move |rule| {
231 rule.targets.is_empty() || rule.targets.iter().any(|t| t == platform)
232 })
233 }
234}
235
236#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
240pub struct MotionToken {
241 pub name: String,
242 pub period: f64,
243 pub damping: f64,
244}
245
246#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
247pub struct StyleRule {
248 pub component: String,
250 pub variant: Option<String>,
252 pub targets: Vec<String>,
255 pub exempt: Vec<String>,
257 pub base: Vec<StyleEffect>,
259 pub states: Vec<StateStyle>,
260 pub settle: Option<String>,
262 pub source: SourceLocation,
263}
264
265#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
266pub struct StateStyle {
267 pub state: StyleState,
268 pub effects: Vec<StyleEffect>,
269}
270
271#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
272#[serde(rename_all = "snake_case")]
273pub enum StyleState {
274 Hover,
275 Pressed,
276 Selected,
277 Focus,
278 Disabled,
279}
280
281impl StyleState {
282 pub const ALL: [StyleState; 5] = [
283 StyleState::Hover,
284 StyleState::Pressed,
285 StyleState::Selected,
286 StyleState::Focus,
287 StyleState::Disabled,
288 ];
289
290 pub fn name(self) -> &'static str {
291 match self {
292 StyleState::Hover => "hover",
293 StyleState::Pressed => "pressed",
294 StyleState::Selected => "selected",
295 StyleState::Focus => "focus",
296 StyleState::Disabled => "disabled",
297 }
298 }
299}
300
301#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
302#[serde(tag = "kind", rename_all = "snake_case")]
303pub enum StyleEffect {
304 Background {
305 color: ColorRef,
306 },
307 Foreground {
308 color: ColorRef,
309 },
310 Border {
311 color: ColorRef,
312 width: f64,
313 },
314 Corner {
315 radius: f64,
316 },
317 Padding {
318 length: f64,
319 },
320 Size {
321 size: ControlSize,
322 },
323 Fill {
326 color: ColorRef,
327 },
328 Indicator {
330 color: ColorRef,
331 edge: Edge,
332 width: f64,
333 },
334 Scale {
335 x: f64,
336 y: f64,
337 motion: String,
338 },
339 Offset {
340 x: f64,
341 y: f64,
342 motion: String,
343 },
344 Opacity {
345 value: f64,
346 motion: String,
347 },
348 Shadow {
351 radius: f64,
352 y: f64,
353 opacity: f64,
354 },
355 System,
357 Nothing,
359 Metric {
363 metric: Metric,
364 value: Option<f64>,
365 },
366}
367
368#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
372#[serde(rename_all = "snake_case")]
373pub enum Metric {
374 Gap,
375 PaddingX,
376 PaddingY,
377 Width,
378 Height,
379 FontSize,
380 RowHeight,
381}
382
383impl Metric {
384 pub const ALL: [Metric; 7] = [
385 Metric::Gap,
386 Metric::PaddingX,
387 Metric::PaddingY,
388 Metric::Width,
389 Metric::Height,
390 Metric::FontSize,
391 Metric::RowHeight,
392 ];
393
394 pub fn name(self) -> &'static str {
396 match self {
397 Metric::Gap => "gap",
398 Metric::PaddingX => "padding_x",
399 Metric::PaddingY => "padding_y",
400 Metric::Width => "width",
401 Metric::Height => "height",
402 Metric::FontSize => "font_size",
403 Metric::RowHeight => "row_height",
404 }
405 }
406}
407
408impl StyleEffect {
409 pub fn name(&self) -> &'static str {
411 match self {
412 StyleEffect::Background { .. } => "background",
413 StyleEffect::Foreground { .. } => "foreground",
414 StyleEffect::Border { .. } => "border",
415 StyleEffect::Corner { .. } => "corner",
416 StyleEffect::Padding { .. } => "padding",
417 StyleEffect::Size { .. } => "size",
418 StyleEffect::Fill { .. } => "fill",
419 StyleEffect::Indicator { .. } => "indicator",
420 StyleEffect::Scale { .. } => "scale",
421 StyleEffect::Offset { .. } => "offset",
422 StyleEffect::Opacity { .. } => "opacity",
423 StyleEffect::Shadow { .. } => "shadow",
424 StyleEffect::System => "system",
425 StyleEffect::Nothing => "none",
426 StyleEffect::Metric { metric, .. } => metric.name(),
427 }
428 }
429}
430
431#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
432#[serde(tag = "kind", rename_all = "snake_case")]
433pub enum ColorRef {
434 Role { role: String },
435 Fixed { light: Srgb, dark: Srgb },
436}
437
438#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
439#[serde(rename_all = "snake_case")]
440pub enum ControlSize {
441 Small,
442 Regular,
443 Large,
444 ExtraLarge,
445}
446
447#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
448#[serde(rename_all = "snake_case")]
449pub enum Edge {
450 Leading,
451 Trailing,
452 Top,
453 Bottom,
454}