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