1use crate::color::Color;
13use crate::text::{WispFontStyle, WispFontWeight, WispTextAlign, WispTextStyle};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum TextPreset {
18 SectionTitle,
20 Caption,
22 Callout,
24 KeyboardShortcut,
28 StepBadge,
30 WarningPrivacyLabel,
32 Watermark,
34}
35
36impl TextPreset {
37 #[must_use]
39 pub fn style(self) -> WispTextStyle {
40 match self {
41 Self::SectionTitle => section_title(),
42 Self::Caption => caption(),
43 Self::Callout => callout(),
44 Self::KeyboardShortcut => keyboard_shortcut(),
45 Self::StepBadge => step_badge(),
46 Self::WarningPrivacyLabel => warning_privacy_label(),
47 Self::Watermark => watermark(),
48 }
49 }
50
51 #[must_use]
55 pub fn all() -> [Self; 7] {
56 [
57 Self::SectionTitle,
58 Self::Caption,
59 Self::Callout,
60 Self::KeyboardShortcut,
61 Self::StepBadge,
62 Self::WarningPrivacyLabel,
63 Self::Watermark,
64 ]
65 }
66
67 #[must_use]
69 pub const fn label(self) -> &'static str {
70 match self {
71 Self::SectionTitle => "Section title",
72 Self::Caption => "Caption",
73 Self::Callout => "Callout",
74 Self::KeyboardShortcut => "Keyboard shortcut",
75 Self::StepBadge => "Step badge",
76 Self::WarningPrivacyLabel => "Warning / privacy label",
77 Self::Watermark => "Watermark",
78 }
79 }
80}
81
82#[must_use]
84pub fn section_title() -> WispTextStyle {
85 WispTextStyle {
86 size_ndc: 0.18,
87 color: Color::rgba_u8(248, 250, 255, 255),
88 line_height: 1.1,
89 letter_spacing_ndc: -0.002,
90 weight: WispFontWeight::Bold,
91 style: WispFontStyle::Normal,
92 align: WispTextAlign::Center,
93 ..WispTextStyle::default()
94 }
95}
96
97#[must_use]
99pub fn caption() -> WispTextStyle {
100 WispTextStyle {
101 size_ndc: 0.075,
102 color: Color::rgba_u8(240, 240, 245, 255),
103 line_height: 1.30,
104 letter_spacing_ndc: 0.0,
105 weight: WispFontWeight::Regular,
106 style: WispFontStyle::Normal,
107 align: WispTextAlign::Left,
108 ..WispTextStyle::default()
109 }
110}
111
112#[must_use]
114pub fn callout() -> WispTextStyle {
115 WispTextStyle {
116 size_ndc: 0.085,
117 color: Color::rgba_u8(255, 235, 200, 255),
118 line_height: 1.25,
119 letter_spacing_ndc: 0.0,
120 weight: WispFontWeight::Medium,
121 style: WispFontStyle::Italic,
122 align: WispTextAlign::Left,
123 ..WispTextStyle::default()
124 }
125}
126
127#[must_use]
129pub fn keyboard_shortcut() -> WispTextStyle {
130 WispTextStyle {
131 size_ndc: 0.055,
132 color: Color::rgba_u8(220, 230, 240, 255),
133 line_height: 1.0,
134 letter_spacing_ndc: 0.002,
135 weight: WispFontWeight::Medium,
136 style: WispFontStyle::Normal,
137 align: WispTextAlign::Center,
138 ..WispTextStyle::default()
139 }
140}
141
142#[must_use]
144pub fn step_badge() -> WispTextStyle {
145 WispTextStyle {
146 size_ndc: 0.05,
147 color: Color::rgba_u8(255, 240, 215, 255),
148 line_height: 1.0,
149 letter_spacing_ndc: 0.004,
150 weight: WispFontWeight::Bold,
151 style: WispFontStyle::Normal,
152 align: WispTextAlign::Center,
153 ..WispTextStyle::default()
154 }
155}
156
157#[must_use]
159pub fn warning_privacy_label() -> WispTextStyle {
160 WispTextStyle {
161 size_ndc: 0.06,
162 color: Color::rgba_u8(255, 100, 90, 255),
163 line_height: 1.0,
164 letter_spacing_ndc: 0.006,
165 weight: WispFontWeight::Bold,
166 style: WispFontStyle::Normal,
167 align: WispTextAlign::Center,
168 ..WispTextStyle::default()
169 }
170}
171
172#[must_use]
174pub fn watermark() -> WispTextStyle {
175 WispTextStyle {
176 size_ndc: 0.04,
177 color: Color::rgba_u8(200, 200, 210, 160), line_height: 1.0,
179 letter_spacing_ndc: 0.001,
180 weight: WispFontWeight::Light,
181 style: WispFontStyle::Italic,
182 align: WispTextAlign::Right,
183 ..WispTextStyle::default()
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn all_returns_seven_presets() {
193 assert_eq!(TextPreset::all().len(), 7);
194 }
195
196 #[test]
197 fn every_preset_has_positive_size() {
198 for p in TextPreset::all() {
199 let style = p.style();
200 assert!(style.size_ndc > 0.0, "{} has non-positive size", p.label());
201 assert!(style.line_height > 0.0);
202 }
203 }
204
205 #[test]
206 fn section_title_is_centered_bold() {
207 let s = section_title();
208 assert_eq!(s.align, WispTextAlign::Center);
209 assert_eq!(s.weight, WispFontWeight::Bold);
210 }
211
212 #[test]
213 fn warning_is_signal_red() {
214 let c = warning_privacy_label().color;
215 assert!(c.r > 0.8 && c.g < 0.5 && c.b < 0.5);
217 }
218
219 #[test]
220 fn watermark_is_low_contrast_and_italic() {
221 let s = watermark();
222 assert_eq!(s.style, WispFontStyle::Italic);
223 assert!(s.color.a < 1.0, "watermark should have alpha < 1");
224 }
225
226 #[test]
227 fn callout_is_italic() {
228 assert_eq!(callout().style, WispFontStyle::Italic);
229 }
230
231 #[test]
232 fn distinct_sizes_across_presets() {
233 let styles: Vec<_> = TextPreset::all().iter().map(|p| p.style()).collect();
235 for i in 0..styles.len() {
236 for j in (i + 1)..styles.len() {
237 assert_ne!(styles[i], styles[j], "presets {i} and {j} are identical");
238 }
239 }
240 }
241
242 #[test]
243 fn labels_are_unique_and_non_empty() {
244 let mut labels: Vec<_> = TextPreset::all().iter().map(|p| p.label()).collect();
245 labels.sort_unstable();
246 labels.dedup();
247 assert_eq!(labels.len(), TextPreset::all().len());
248 for label in &labels {
249 assert!(!label.is_empty());
250 }
251 }
252}