1#![warn(missing_docs)]
30#![forbid(unsafe_code)]
31
32pub mod extended;
33pub mod icons;
34pub mod palette;
35
36#[deprecated(since = "0.3.2", note = "Use NativeTheme::pick_variant() instead")]
43#[allow(deprecated)]
44pub fn pick_variant(
45 theme: &native_theme::NativeTheme,
46 is_dark: bool,
47) -> Option<&native_theme::ThemeVariant> {
48 theme.pick_variant(is_dark)
49}
50
51pub fn to_theme(variant: &native_theme::ThemeVariant, name: &str) -> iced_core::theme::Theme {
64 let pal = palette::to_palette(variant);
65
66 let colors = variant.colors.clone();
69
70 iced_core::theme::Theme::custom_with_fn(name.to_string(), pal, move |p| {
71 let mut ext = iced_core::theme::palette::Extended::generate(p);
72
73 let mut tmp = native_theme::ThemeVariant::default();
75 tmp.colors = colors;
76 extended::apply_overrides(&mut ext, &tmp);
77
78 ext
79 })
80}
81
82pub fn button_padding(variant: &native_theme::ThemeVariant) -> Option<[f32; 2]> {
87 let bm = &variant.widget_metrics.as_ref()?.button;
88 let h = bm.padding_horizontal?;
89 let v = bm.padding_vertical.unwrap_or(h * 0.5);
90 Some([h, v])
91}
92
93pub fn input_padding(variant: &native_theme::ThemeVariant) -> Option<[f32; 2]> {
98 let im = &variant.widget_metrics.as_ref()?.input;
99 let h = im.padding_horizontal?;
100 let v = im.padding_vertical.unwrap_or(h * 0.5);
101 Some([h, v])
102}
103
104pub fn border_radius(variant: &native_theme::ThemeVariant) -> f32 {
106 variant.geometry.radius.unwrap_or(4.0)
107}
108
109pub fn border_radius_lg(variant: &native_theme::ThemeVariant) -> f32 {
111 variant.geometry.radius_lg.unwrap_or(8.0)
112}
113
114pub fn scrollbar_width(variant: &native_theme::ThemeVariant) -> f32 {
118 variant
120 .geometry
121 .scroll_width
122 .or_else(|| {
123 variant
124 .widget_metrics
125 .as_ref()
126 .and_then(|wm| wm.scrollbar.width)
127 })
128 .unwrap_or(10.0)
129}
130
131pub fn font_family(variant: &native_theme::ThemeVariant) -> Option<&str> {
133 variant.fonts.family.as_deref()
134}
135
136pub fn font_size(variant: &native_theme::ThemeVariant) -> Option<f32> {
141 variant.fonts.size.map(|pt| pt * (96.0 / 72.0))
142}
143
144pub fn mono_font_family(variant: &native_theme::ThemeVariant) -> Option<&str> {
146 variant.fonts.mono_family.as_deref()
147}
148
149pub fn mono_font_size(variant: &native_theme::ThemeVariant) -> Option<f32> {
154 variant.fonts.mono_size.map(|pt| pt * (96.0 / 72.0))
155}
156
157#[cfg(test)]
158#[allow(deprecated)]
159mod tests {
160 use super::*;
161 use native_theme::{NativeTheme, Rgba, ThemeVariant};
162
163 #[test]
166 fn pick_variant_light_preferred_returns_light() {
167 let mut theme = NativeTheme::new("Test");
168 theme.light = Some(ThemeVariant::default());
169 theme.dark = Some(ThemeVariant::default());
170
171 let result = pick_variant(&theme, false);
172 assert!(result.is_some());
173 assert!(std::ptr::eq(result.unwrap(), theme.light.as_ref().unwrap()));
176 }
177
178 #[test]
179 fn pick_variant_dark_preferred_returns_dark() {
180 let mut theme = NativeTheme::new("Test");
181 theme.light = Some(ThemeVariant::default());
182 theme.dark = Some(ThemeVariant::default());
183
184 let result = pick_variant(&theme, true);
185 assert!(result.is_some());
186 assert!(std::ptr::eq(result.unwrap(), theme.dark.as_ref().unwrap()));
187 }
188
189 #[test]
190 fn pick_variant_falls_back_to_light_when_no_dark() {
191 let mut theme = NativeTheme::new("Test");
192 theme.light = Some(ThemeVariant::default());
193 let result = pick_variant(&theme, true);
196 assert!(result.is_some());
197 assert!(std::ptr::eq(result.unwrap(), theme.light.as_ref().unwrap()));
198 }
199
200 #[test]
201 fn pick_variant_falls_back_to_dark_when_no_light() {
202 let mut theme = NativeTheme::new("Test");
203 theme.dark = Some(ThemeVariant::default());
205
206 let result = pick_variant(&theme, false);
207 assert!(result.is_some());
208 assert!(std::ptr::eq(result.unwrap(), theme.dark.as_ref().unwrap()));
209 }
210
211 #[test]
212 fn pick_variant_returns_none_when_empty() {
213 let theme = NativeTheme::new("Test");
214 assert!(pick_variant(&theme, false).is_none());
215 assert!(pick_variant(&theme, true).is_none());
216 }
217
218 #[test]
221 fn to_theme_produces_non_default_theme() {
222 let mut variant = ThemeVariant::default();
223 variant.colors.accent = Some(Rgba::rgb(0, 120, 215));
224 variant.colors.background = Some(Rgba::rgb(30, 30, 30));
225 variant.colors.foreground = Some(Rgba::rgb(220, 220, 220));
226
227 let theme = to_theme(&variant, "Test Theme");
228
229 assert_ne!(theme, iced_core::theme::Theme::Light);
231 assert_ne!(theme, iced_core::theme::Theme::Dark);
232
233 let palette = theme.palette();
235 assert!(
236 (palette.primary.r - 0.0).abs() < 0.01,
237 "primary.r should be ~0.0, got {}",
238 palette.primary.r
239 );
240 }
241
242 #[test]
243 fn to_theme_from_preset() {
244 let nt = NativeTheme::preset("default").unwrap();
245 let variant = pick_variant(&nt, false).unwrap();
246 let theme = to_theme(variant, "Default");
247
248 let palette = theme.palette();
250 assert!(palette.background.r > 0.9);
252 }
253
254 #[test]
257 fn border_radius_returns_geometry_value() {
258 let mut variant = ThemeVariant::default();
259 variant.geometry.radius = Some(6.0);
260
261 assert_eq!(border_radius(&variant), 6.0);
262 }
263
264 #[test]
265 fn border_radius_returns_default_when_none() {
266 let variant = ThemeVariant::default();
267 assert_eq!(border_radius(&variant), 4.0);
268 }
269
270 #[test]
271 fn border_radius_lg_returns_geometry_value() {
272 let mut variant = ThemeVariant::default();
273 variant.geometry.radius_lg = Some(12.0);
274
275 assert_eq!(border_radius_lg(&variant), 12.0);
276 }
277
278 #[test]
279 fn border_radius_lg_returns_default_when_none() {
280 let variant = ThemeVariant::default();
281 assert_eq!(border_radius_lg(&variant), 8.0);
282 }
283
284 #[test]
285 fn scrollbar_width_prefers_geometry() {
286 let mut variant = ThemeVariant::default();
287 variant.geometry.scroll_width = Some(14.0);
288
289 assert_eq!(scrollbar_width(&variant), 14.0);
290 }
291
292 #[test]
293 fn scrollbar_width_falls_back_to_widget_metrics() {
294 let mut variant = ThemeVariant::default();
295 let mut wm = native_theme::WidgetMetrics::default();
296 wm.scrollbar.width = Some(12.0);
297 variant.widget_metrics = Some(wm);
298
299 assert_eq!(scrollbar_width(&variant), 12.0);
300 }
301
302 #[test]
303 fn scrollbar_width_returns_default_when_none() {
304 let variant = ThemeVariant::default();
305 assert_eq!(scrollbar_width(&variant), 10.0);
306 }
307
308 #[test]
309 fn button_padding_returns_values_from_metrics() {
310 let mut variant = ThemeVariant::default();
311 let mut wm = native_theme::WidgetMetrics::default();
312 wm.button.padding_horizontal = Some(12.0);
313 wm.button.padding_vertical = Some(6.0);
314 variant.widget_metrics = Some(wm);
315
316 let result = button_padding(&variant).unwrap();
317 assert_eq!(result, [12.0, 6.0]);
318 }
319
320 #[test]
321 fn button_padding_returns_none_without_metrics() {
322 let variant = ThemeVariant::default();
323 assert!(button_padding(&variant).is_none());
324 }
325
326 #[test]
327 fn input_padding_returns_values_from_metrics() {
328 let mut variant = ThemeVariant::default();
329 let mut wm = native_theme::WidgetMetrics::default();
330 wm.input.padding_horizontal = Some(8.0);
331 wm.input.padding_vertical = Some(4.0);
332 variant.widget_metrics = Some(wm);
333
334 let result = input_padding(&variant).unwrap();
335 assert_eq!(result, [8.0, 4.0]);
336 }
337
338 #[test]
339 fn input_padding_returns_none_without_metrics() {
340 let variant = ThemeVariant::default();
341 assert!(input_padding(&variant).is_none());
342 }
343
344 #[test]
347 fn font_family_returns_value() {
348 let mut variant = ThemeVariant::default();
349 variant.fonts.family = Some("Inter".into());
350 assert_eq!(font_family(&variant), Some("Inter"));
351 }
352
353 #[test]
354 fn font_family_returns_none_when_unset() {
355 let variant = ThemeVariant::default();
356 assert!(font_family(&variant).is_none());
357 }
358
359 #[test]
360 fn font_size_converts_points_to_pixels() {
361 let mut variant = ThemeVariant::default();
362 variant.fonts.size = Some(12.0);
363 let px = font_size(&variant).unwrap();
364 assert!((px - 16.0).abs() < 0.01, "12pt should be 16px, got {px}");
365 }
366
367 #[test]
368 fn font_size_returns_none_when_unset() {
369 let variant = ThemeVariant::default();
370 assert!(font_size(&variant).is_none());
371 }
372
373 #[test]
374 fn mono_font_family_returns_value() {
375 let mut variant = ThemeVariant::default();
376 variant.fonts.mono_family = Some("JetBrains Mono".into());
377 assert_eq!(mono_font_family(&variant), Some("JetBrains Mono"));
378 }
379
380 #[test]
381 fn mono_font_size_converts_points_to_pixels() {
382 let mut variant = ThemeVariant::default();
383 variant.fonts.mono_size = Some(10.0);
384 let px = mono_font_size(&variant).unwrap();
385 let expected = 10.0 * (96.0 / 72.0);
386 assert!(
387 (px - expected).abs() < 0.01,
388 "10pt should be {expected}px, got {px}"
389 );
390 }
391}