Skip to main content

floem/style/
theme.rs

1use std::time::Duration;
2
3use super::unit::{DurationUnitExt, UnitExt};
4use super::*;
5use crate::style::Selectable;
6use crate::view::View;
7use crate::views::editor::SelectionColor;
8use crate::views::resizable::{ResizableClass, ResizableHandleClass};
9use crate::{
10    AnyView, prop, style_class, style_debug_group,
11    views::{
12        ButtonClass, CheckboxClass, LabelClass, LabelCustomExprStyle, LabelCustomStyle,
13        LabeledCheckboxClass, LabeledRadioButtonClass, ListClass, ListItemClass,
14        PlaceholderTextClass, RadioButtonClass, RadioButtonDotClass, SvgClass, TabSelectorClass,
15        TextInputClass, ToggleButtonCircleRad, ToggleButtonClass, ToggleButtonInset, TooltipClass,
16        dropdown,
17        resizable::{ResizableCustomExprStyle, ResizableCustomStyle},
18        scroll,
19        slider::{SliderClass, SliderCustomExprStyle, SliderCustomStyle},
20    },
21};
22use floem_renderer::text::FontWeight;
23use peniko::{Brush, Color, color::palette::css};
24use smallvec::smallvec;
25
26style_class!(pub HoverTargetClass);
27
28fn border_debug_view(style: &Style) -> Option<Box<dyn View>> {
29    Border {
30        left: style.get_prop::<BorderLeft>(),
31        top: style.get_prop::<BorderTop>(),
32        right: style.get_prop::<BorderRight>(),
33        bottom: style.get_prop::<BorderBottom>(),
34    }
35    .debug_view()
36}
37
38fn border_color_debug_view(style: &Style) -> Option<Box<dyn View>> {
39    BorderColor {
40        left: style.get_prop::<BorderLeftColor>().flatten(),
41        top: style.get_prop::<BorderTopColor>().flatten(),
42        right: style.get_prop::<BorderRightColor>().flatten(),
43        bottom: style.get_prop::<BorderBottomColor>().flatten(),
44    }
45    .debug_view()
46}
47
48fn border_radius_debug_view(style: &Style) -> Option<Box<dyn View>> {
49    BorderRadius {
50        top_left: style.get_prop::<BorderTopLeftRadius>(),
51        top_right: style.get_prop::<BorderTopRightRadius>(),
52        bottom_left: style.get_prop::<BorderBottomLeftRadius>(),
53        bottom_right: style.get_prop::<BorderBottomRightRadius>(),
54    }
55    .debug_view()
56}
57
58fn padding_debug_view(style: &Style) -> Option<Box<dyn View>> {
59    Padding {
60        left: style.get_prop::<PaddingLeft>(),
61        top: style.get_prop::<PaddingTop>(),
62        right: style.get_prop::<PaddingRight>(),
63        bottom: style.get_prop::<PaddingBottom>(),
64    }
65    .debug_view()
66}
67
68fn margin_debug_view(style: &Style) -> Option<Box<dyn View>> {
69    Margin {
70        left: style.get_prop::<MarginLeft>(),
71        top: style.get_prop::<MarginTop>(),
72        right: style.get_prop::<MarginRight>(),
73        bottom: style.get_prop::<MarginBottom>(),
74    }
75    .debug_view()
76}
77
78style_debug_group!(
79    pub BorderDebugGroup,
80    inherited = inherited,
81    members = [BorderLeft, BorderTop, BorderRight, BorderBottom],
82    view = border_debug_view
83);
84style_debug_group!(
85    pub BorderColorDebugGroup,
86    inherited = inherited,
87    members = [BorderLeftColor, BorderTopColor, BorderRightColor, BorderBottomColor],
88    view = border_color_debug_view
89);
90style_debug_group!(
91    pub BorderRadiusDebugGroup,
92    inherited = inherited,
93    members = [BorderTopLeftRadius, BorderTopRightRadius, BorderBottomLeftRadius, BorderBottomRightRadius],
94    view = border_radius_debug_view
95);
96style_debug_group!(
97    pub PaddingDebugGroup,
98    inherited = inherited,
99    members = [PaddingLeft, PaddingTop, PaddingRight, PaddingBottom],
100    view = padding_debug_view
101);
102style_debug_group!(
103    pub MarginDebugGroup,
104    inherited = inherited,
105    members = [MarginLeft, MarginTop, MarginRight, MarginBottom],
106    view = margin_debug_view
107);
108
109#[derive(Debug, Clone, PartialEq)]
110pub struct DesignSystem {
111    pub bg_base: Color,
112    pub text_base: Color,
113    pub text_lightness: f32,
114    pub primary_base: Color,
115    pub success_base: Color,
116    pub warning_base: Color,
117    pub danger_base: Color,
118    pub is_dark: bool,
119    pub padding: f32,
120    pub border_radius: f32,
121    pub font_size: f64,
122}
123// const BORDER_RADIUS: f32 = 5.0;
124// const FONT_SIZE: f32 = 12.0;
125
126impl DesignSystem {
127    /// Create a light mode design system.
128    pub fn light() -> Self {
129        Self {
130            bg_base: Color::from_rgb8(248, 248, 248),
131            text_base: Color::from_rgb8(0, 0, 0),
132            text_lightness: 0.05,
133            primary_base: Color::from_rgb8(0x18, 0x96, 0xC2),
134            success_base: Color::from_rgb8(0x2D, 0x9D, 0x67),
135            warning_base: Color::from_rgb8(0xE5, 0xA2, 0x23),
136            danger_base: Color::from_rgb8(0xD7, 0x37, 0x45),
137            padding: 5.,
138            border_radius: 5.,
139            font_size: 14.,
140            is_dark: false,
141        }
142    }
143
144    /// Create a dark mode design system.
145    pub fn dark() -> Self {
146        Self {
147            bg_base: Color::from_rgb8(0x24, 0x24, 0x24),
148            text_base: Color::from_rgb8(255, 255, 255),
149            text_lightness: 0.95,
150            primary_base: Color::from_rgb8(0x3A, 0xAA, 0xD8),
151            success_base: Color::from_rgb8(0x4A, 0xBE, 0x8A),
152            warning_base: Color::from_rgb8(0xF5, 0xB8, 0x4E),
153            danger_base: Color::from_rgb8(0xF0, 0x56, 0x54),
154            padding: 5.,
155            border_radius: 5.,
156            font_size: 14.,
157            is_dark: true,
158        }
159    }
160
161    // Background levels
162
163    pub fn bg_base(&self) -> Color {
164        self.bg_base
165    }
166
167    pub fn bg_elevated(&self) -> Color {
168        let adjustment = 0.05;
169        self.bg_base.map_lightness(|l| l + adjustment)
170    }
171
172    pub fn bg_overlay(&self) -> Color {
173        let adjustment = 0.10;
174        self.bg_base.map_lightness(|l| l + adjustment)
175    }
176
177    pub fn bg_disabled(&self) -> Color {
178        let adjustment = if self.is_dark { -0.05 } else { -0.1 };
179        self.bg_base.map_lightness(|l| l + adjustment)
180    }
181
182    // Border
183
184    pub fn border(&self) -> Color {
185        let adjustment = if self.is_dark { 0.25 } else { -0.25 };
186        self.bg_base.map_lightness(|l| l + adjustment)
187    }
188
189    pub fn border_muted(&self) -> Color {
190        let adjustment = if self.is_dark { 0.15 } else { -0.15 };
191        self.border()
192            .map_lightness(|l| l + adjustment)
193            .with_alpha(0.8)
194    }
195
196    // Text
197
198    pub fn text(&self) -> Color {
199        self.text_base.map_lightness(|_| self.text_lightness)
200    }
201
202    pub fn text_muted(&self) -> Color {
203        let adjustment = if self.is_dark { -0.25 } else { 0.25 };
204        self.text_base
205            .map_lightness(|l| l + adjustment)
206            .with_alpha(0.5)
207    }
208
209    // Primary
210
211    pub fn primary(&self) -> Color {
212        self.primary_base
213    }
214
215    pub fn primary_muted(&self) -> Color {
216        self.primary_base.map_lightness(|l| l - 0.05)
217    }
218
219    // Semantic colors
220
221    pub fn success(&self) -> Color {
222        self.success_base
223    }
224
225    pub fn warning(&self) -> Color {
226        self.warning_base
227    }
228
229    pub fn danger(&self) -> Color {
230        self.danger_base
231    }
232
233    pub fn info(&self) -> Color {
234        self.primary_base
235    }
236
237    pub fn padding(&self) -> f32 {
238        self.padding
239    }
240
241    pub fn border_radius(&self) -> f32 {
242        self.border_radius
243    }
244
245    pub fn font_size(&self) -> f64 {
246        self.font_size
247    }
248}
249
250impl StylePropValue for DesignSystem {
251    fn debug_view(&self) -> Option<AnyView> {
252        use crate::prelude::*;
253        use crate::views::Stack;
254
255        let design_system = self.clone();
256        let is_expanded = RwSignal::new(false);
257
258        let color_swatch = |label: &str, color: Color| {
259            Stack::new((
260                label.to_string().style(|s| s.width(120.0).font_size(12.0)),
261                color.debug_view().unwrap(),
262            ))
263            .style(|s| s.flex_row().items_center().gap(8.0).padding_vert(2.0))
264        };
265
266        let scalar_field = |label: &str, value: f64| {
267            Stack::new((
268                label.to_string().style(|s| s.width(120.0).font_size(12.0)),
269                format!("{:.2}", value).style(|s| s.font_size(12.0)),
270            ))
271            .style(|s| s.flex_row().items_center().gap(8.0).padding_vert(2.0))
272        };
273
274        let chevron = move || {
275            if is_expanded.get() {
276                svg(
277                    r#"<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M4.427 6.427l3.396 3.396a.25.25 0 00.354 0l3.396-3.396A.25.25 0 0011.396 6H4.604a.25.25 0 00-.177.427z"/></svg>"#,
278                )
279            } else {
280                svg(
281                    r#"<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M6.427 4.427l3.396 3.396a.25.25 0 010 .354l-3.396 3.396A.25.25 0 016 11.396V4.604a.25.25 0 01.427-.177z"/></svg>"#,
282                )
283            }.style(|s| s.size_full().with_theme(|s, t| s.color(t.text())))
284        };
285
286        let header = Stack::new((
287            dyn_view(chevron)
288                .class(ButtonClass)
289                .style(|s| s.size(16.0, 16.0).padding(0.)),
290            "Design System"
291                .to_string()
292                .style(|s| s.font_size(14.0).font_weight(FontWeight::SEMI_BOLD)),
293        ))
294        .action(move || {
295            is_expanded.update(|v| *v = !*v);
296        })
297        .style(|s| {
298            s.flex_row()
299                .items_center()
300                .gap(8.0)
301                .cursor(CursorStyle::Pointer)
302        });
303
304        let content = Stack::new((
305            header,
306            Stack::new((
307                color_swatch("bg_base", design_system.bg_base),
308                color_swatch("text_base", design_system.text_base),
309                color_swatch("primary_base", design_system.primary_base),
310                color_swatch("success_base", design_system.success_base),
311                color_swatch("warning_base", design_system.warning_base),
312                color_swatch("danger_base", design_system.danger_base),
313                scalar_field("text_lightness", f64::from(design_system.text_lightness)),
314                scalar_field("padding", f64::from(design_system.padding)),
315                scalar_field("border_radius", f64::from(design_system.border_radius)),
316                scalar_field("font_size", design_system.font_size),
317                format!("is_dark: {}", design_system.is_dark).style(|s| s.font_size(12.0)),
318            ))
319            .style(move |s| s.flex_col().gap(4.0))
320            .clip()
321            .style(move |s| {
322                s.height_pct(100.)
323                    .apply_if(!is_expanded.get(), |s| s.height_pct(0.))
324                    .transition_height(Transition::ease_in_out(Duration::from_millis(200)))
325            }),
326        ))
327        .style(|s| {
328            // this view here should be getting set to have a height of just the two children combined
329            // I think this is a bug in taffy
330            s.flex_col()
331                .padding(8.0)
332                .border(1.)
333                .border_color(palette::css::WHITE.with_alpha(0.3))
334                .border_radius(6.0)
335                .min_width(280.0)
336                .min_height_pct(0.)
337                .flex_grow(0.)
338                .flex_shrink(1.)
339        });
340
341        Some(content.into_any())
342    }
343
344    fn interpolate(&self, other: &Self, value: f64) -> Option<Self> {
345        use peniko::color::HueDirection;
346        let t = value as f32;
347        let inv_t = 1.0 - t;
348        let t64 = value;
349        let inv_t64 = 1.0 - t64;
350
351        Some(DesignSystem {
352            bg_base: self.bg_base.lerp(other.bg_base, t, HueDirection::default()),
353            text_base: self
354                .text_base
355                .lerp(other.text_base, t, HueDirection::default()),
356            text_lightness: self.text_lightness * inv_t + other.text_lightness * t,
357            primary_base: self
358                .primary_base
359                .lerp(other.primary_base, t, HueDirection::default()),
360            success_base: self
361                .success_base
362                .lerp(other.success_base, t, HueDirection::default()),
363            warning_base: self
364                .warning_base
365                .lerp(other.warning_base, t, HueDirection::default()),
366            danger_base: self
367                .danger_base
368                .lerp(other.danger_base, t, HueDirection::default()),
369            is_dark: if t < 0.5 { self.is_dark } else { other.is_dark },
370            padding: self.padding * inv_t + other.padding * t,
371            border_radius: self.border_radius * inv_t + other.border_radius * t,
372            font_size: self.font_size * inv_t64 + other.font_size * t64,
373        })
374    }
375}
376
377prop!(
378    pub Theme: DesignSystem { inherited } = DesignSystem::light()
379);
380
381#[derive(Clone, Copy)]
382pub struct ThemeExpr(pub(crate) ContextRef<Theme>);
383
384impl ThemeExpr {
385    pub fn def<T>(self, f: impl Fn(DesignSystem) -> T + 'static) -> ContextValue<T>
386    where
387        T: 'static,
388    {
389        self.0.def(f)
390    }
391
392    pub fn bg_base(self) -> ContextValue<Color> {
393        self.def(|t| t.bg_base())
394    }
395    pub fn bg_elevated(self) -> ContextValue<Color> {
396        self.def(|t| t.bg_elevated())
397    }
398    pub fn bg_overlay(self) -> ContextValue<Color> {
399        self.def(|t| t.bg_overlay())
400    }
401    pub fn bg_disabled(self) -> ContextValue<Color> {
402        self.def(|t| t.bg_disabled())
403    }
404    pub fn border(self) -> ContextValue<Color> {
405        self.def(|t| t.border())
406    }
407    pub fn border_muted(self) -> ContextValue<Color> {
408        self.def(|t| t.border_muted())
409    }
410    pub fn text(self) -> ContextValue<Color> {
411        self.def(|t| t.text())
412    }
413    pub fn text_muted(self) -> ContextValue<Color> {
414        self.def(|t| t.text_muted())
415    }
416    pub fn primary(self) -> ContextValue<Color> {
417        self.def(|t| t.primary())
418    }
419    pub fn primary_muted(self) -> ContextValue<Color> {
420        self.def(|t| t.primary_muted())
421    }
422    pub fn success(self) -> ContextValue<Color> {
423        self.def(|t| t.success())
424    }
425    pub fn warning(self) -> ContextValue<Color> {
426        self.def(|t| t.warning())
427    }
428    pub fn danger(self) -> ContextValue<Color> {
429        self.def(|t| t.danger())
430    }
431    pub fn info(self) -> ContextValue<Color> {
432        self.def(|t| t.info())
433    }
434    pub fn padding(self) -> ContextValue<Length> {
435        self.def(|t| t.padding().into())
436    }
437    pub fn border_radius(self) -> ContextValue<Length> {
438        self.def(|t| t.border_radius().into())
439    }
440    pub fn font_size(self) -> ContextValue<f64> {
441        self.def(|t| t.font_size())
442    }
443    pub fn is_dark(self) -> ContextValue<bool> {
444        self.def(|t| t.is_dark)
445    }
446    pub fn warning_base(self) -> ContextValue<Color> {
447        self.def(|t| t.warning_base)
448    }
449}
450
451pub trait StyleThemeExt {
452    fn theme(self, theme: DesignSystem) -> Self;
453    fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self
454    where
455        Self: std::marker::Sized;
456}
457
458impl StyleThemeExt for Style {
459    fn theme(self, theme: DesignSystem) -> Self {
460        self.set(Theme, theme)
461    }
462    fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self {
463        self.with::<Theme>(|s, t| f(s, ThemeExpr(t)))
464    }
465}
466
467impl StyleThemeExt for ExprStyle {
468    fn theme(self, theme: DesignSystem) -> Self {
469        self.set(Theme, theme)
470    }
471    fn with_theme(self, f: impl Fn(ExprStyle, ThemeExpr) -> ExprStyle + 'static) -> Self {
472        self.with::<Theme>(|s, t| f(s, ThemeExpr(t)))
473    }
474}
475
476// pub fn hover_style() -> Style {
477//     Style::new().hover(|s| s.with::<Theme>(|s, t| s.translate_x(t.def(|t| t.padding))))
478// }
479pub fn hover_style() -> Style {
480    Style::new().hover(|s| s.with_theme(|s, t| s.background(t.def(|t| t.bg_elevated()))))
481}
482
483pub fn focus_style() -> Style {
484    let focus_visible_applied_style = Style::new().outline(3.0);
485
486    Style::new()
487        .keyboard_navigable()
488        .with::<Theme>(|s, t| s.outline_color(t.def(|t| t.primary().with_alpha(0.5))))
489        .focus_visible(|_| focus_visible_applied_style.clone())
490}
491
492pub fn border_style(with_radius: bool) -> Style {
493    Style::new()
494        .with_theme(move |s, t| {
495            s.border_color(t.border())
496                .disabled(|s| s.border_color(t.border()))
497                .padding(t.padding())
498                .apply_if(with_radius, |s| s.border_radius(t.border_radius()))
499        })
500        .border(1.0)
501}
502
503pub fn item_selected_style() -> Style {
504    Style::new().selected(|s| {
505        s.with_theme(|s, t| {
506            s.background(t.primary())
507                .color(t.bg_base())
508                .hover(|s| s.background(t.primary_muted()))
509        })
510        .transition_background(Transition::linear(100.millis()))
511    })
512}
513
514pub fn overlay_style() -> Style {
515    Style::new()
516        .with_theme(|s, t| {
517            let shadow_color = Color::from_rgb8(0, 0, 0);
518            s.border_color(t.border())
519                .border_radius(t.border_radius())
520                .padding(t.padding())
521                .color(t.text())
522                .background(t.bg_overlay())
523                .set_context(
524                    BoxShadowProp,
525                    t.def(move |theme| {
526                        let base_opacity = if theme.is_dark { 0.7 } else { 0.18 };
527                        smallvec![
528                            BoxShadow::new()
529                                .color(shadow_color.with_alpha(base_opacity * 1.2))
530                                .v_offset(1.)
531                                .blur_radius(2.)
532                                .spread(0.),
533                            BoxShadow::new()
534                                .color(shadow_color.with_alpha(base_opacity * 0.8))
535                                .v_offset(4.)
536                                .blur_radius(8.)
537                                .spread(-1.),
538                            BoxShadow::new()
539                                .color(shadow_color.with_alpha(base_opacity * 0.5))
540                                .v_offset(12.)
541                                .blur_radius(24.)
542                                .spread(-4.),
543                        ]
544                    }),
545                )
546        })
547        .dark_mode(|s| s.border(1).border_top(2.))
548}
549
550pub(crate) fn default_theme(os_theme: winit::window::Theme) -> Style {
551    let button_style = Style::new()
552        .selectable(false)
553        .with_theme(|s, t| {
554            s.background(t.bg_elevated())
555                .padding(t.padding())
556                .disabled(|s| {
557                    s.background(t.bg_disabled())
558                        .color(t.text_muted())
559                        .unset_cursor()
560                })
561                .hover(|s| s.background(t.bg_overlay()))
562                .active(move |s| {
563                    s.background(t.def(|theme| {
564                        let adjustment = if theme.is_dark { 0.1 } else { -0.2 };
565                        Brush::Solid(theme.bg_overlay().map_lightness(|l| l + adjustment))
566                    }))
567                })
568        })
569        .transition(Background, Transition::linear(100.millis()))
570        .justify_center()
571        .items_center()
572        .cursor(CursorStyle::Pointer)
573        .apply(focus_style())
574        .apply(border_style(true));
575
576    let checkbox_style = Style::new()
577        .size(20, 20)
578        .with_theme(|s, t| {
579            s.background(t.bg_base())
580                .active(|s| s.background(t.bg_elevated()))
581                .disabled(|s| {
582                    s.background(t.def(|t| t.bg_elevated().with_alpha(0.3)))
583                        .color(t.text_muted())
584                        .unset_cursor()
585                })
586        })
587        .transition(Background, Transition::linear(100.millis()))
588        .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
589        .cursor(CursorStyle::Pointer)
590        .apply(border_style(true))
591        .apply(hover_style())
592        .apply(focus_style());
593
594    let labeled_checkbox_style = Style::new()
595        .with_theme(|s, t| {
596            s.hover(|s| s.background(t.def(|t| t.primary_muted().with_alpha(0.7))))
597                .col_gap(t.padding())
598                .padding(t.padding())
599                .border_radius(t.border_radius())
600                .active(|s| {
601                    s.class(CheckboxClass, |s| s.background(t.primary()))
602                        .background(t.primary())
603                })
604                .disabled(|s| {
605                    s.unset_cursor()
606                        .color(t.text_muted())
607                        .class(CheckboxClass, |s| {
608                            s.background(t.bg_disabled())
609                                .color(t.text_muted())
610                                .hover(|s| s.background(t.def(|t| t.bg_elevated().with_alpha(0.3))))
611                        })
612                })
613        })
614        .cursor(CursorStyle::Pointer)
615        .transition(Background, Transition::linear(100.millis()))
616        .class(CheckboxClass, |s| s.focus_none())
617        .selectable(false)
618        .focus(|s| {
619            s.class(CheckboxClass, |s| {
620                s.with_theme(|s, t| s.border_color(t.primary()))
621            })
622            .with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay())))
623        })
624        .apply(hover_style())
625        .apply(focus_style());
626
627    let radio_button_style = Style::new()
628        .size(20, 20)
629        .items_center()
630        .justify_center()
631        .with_theme(|s, t| {
632            s.background(t.bg_base())
633                .active(|s| s.background(t.bg_base()))
634                .hover(|s| s.background(t.bg_elevated()))
635                .disabled(|s| {
636                    s.background(t.bg_disabled())
637                        .color(t.text_muted())
638                        .unset_cursor()
639                })
640        })
641        .cursor(CursorStyle::Pointer)
642        .transition(Background, Transition::linear(100.millis()))
643        .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
644        .border_radius(100.pct())
645        .flex_shrink(0.)
646        .apply(border_style(false))
647        .apply(focus_style());
648
649    let radio_button_dot_style = Style::new()
650        .size(8, 8)
651        .border_radius(100.0)
652        .with_theme(|s, t| {
653            s.background(t.text()).disabled(|s| {
654                s.background(t.text_muted())
655                    .hover(|s| s.background(t.text_muted()))
656            })
657        });
658
659    let labeled_radio_button_style = Style::new()
660        .with_theme(move |s, t| {
661            s.col_gap(t.padding())
662                .padding(t.padding())
663                .set(Selectable, false)
664                .border_radius(t.border_radius())
665                .hover(|s| s.background(t.def(|t| t.primary_muted().with_alpha(0.7))))
666                .active(|s| s.class(RadioButtonClass, |s| s.background(t.bg_elevated())))
667                .selected(|s| s.disabled(|s| s.color(t.bg_elevated())))
668                .disabled(|s| s.color(t.text_muted()).unset_cursor())
669        })
670        .cursor(CursorStyle::Pointer)
671        .class(RadioButtonClass, |s| s.focus_none())
672        .transition(Background, Transition::linear(100.millis()))
673        .focus(|s| {
674            s.with_theme(|s, t| s.hover(|s| s.background(t.def(|t| t.primary().with_alpha(0.7)))))
675        });
676
677    let toggle_button_style = Style::new()
678        .height(1.75.em())
679        .with_theme(|s, t| {
680            s.background(t.bg_elevated())
681                .padding(t.padding())
682                .set_context_opt(Foreground, t.def(|t| Some(Brush::Solid(t.text_muted()))))
683                .active(|s| {
684                    s.background(t.primary())
685                        .color(t.bg_base())
686                        .set_context_opt(Foreground, t.def(|t| Some(Brush::Solid(t.bg_base()))))
687                })
688                .hover(|s| s.background(t.bg_overlay()))
689        })
690        .aspect_ratio(2.)
691        // .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
692        .border_radius(50.pct())
693        .set(ToggleButtonCircleRad, 75.pct())
694        .set(ToggleButtonInset, 10.pct())
695        .apply(border_style(false))
696        .apply(focus_style());
697
698    let input_style = Style::new()
699        .with_theme(|s, t| {
700            s.background(t.bg_base())
701                .padding(t.padding())
702                .set_context(
703                    SelectionColor,
704                    t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))),
705                )
706                .cursor_color(t.primary_muted())
707                .hover(|s| s.background(t.bg_elevated()))
708                .disabled(|s| {
709                    s.background(t.bg_disabled())
710                        .color(t.text_muted())
711                        .unset_cursor()
712                })
713        })
714        .focus(|s| s.with_theme(|s, t| s.hover(|s| s.background(t.bg_overlay()))))
715        .apply(border_style(true))
716        .apply(focus_style())
717        .cursor(CursorStyle::Text);
718
719    let tab_selector_style = Style::new()
720        .custom_style_class(|s: LabelCustomStyle| s.selectable(false))
721        .with_theme(|s, t| {
722            s.background(t.bg_base())
723                .padding(t.padding())
724                .color(t.text_muted())
725                .border_bottom(2.)
726                .disabled(|s| s.background(t.bg_disabled()).color(t.text_muted()))
727                .selected(|s| {
728                    s.background(t.bg_elevated())
729                        .color(t.text())
730                        .border_color(t.primary())
731                })
732                .hover(|s| s.background(t.bg_elevated()).color(t.text()))
733        })
734        .border_color(Color::TRANSPARENT)
735        .transition(Background, Transition::linear(100.millis()))
736        .transition(Foreground, Transition::linear(100.millis()))
737        .justify_center()
738        .items_center()
739        .text_clip()
740        .selectable(false)
741        .apply(focus_style())
742        .apply(hover_style());
743
744    // let item_unfocused_style = Style::new().with_theme(|s, t| {
745    //     s.hover(|s| s.background(t.bg_elevated())).selected(|s| {
746    //         s.background(t.bg_elevated())
747    //             .hover(|s| s.background(t.bg_overlay()))
748    //     })
749    // });
750
751    Style::new()
752        .debug_group(BorderDebugGroup)
753        .debug_group(BorderColorDebugGroup)
754        .debug_group(BorderRadiusDebugGroup)
755        .debug_group(PaddingDebugGroup)
756        .debug_group(MarginDebugGroup)
757        .apply_if(os_theme == winit::window::Theme::Light, |s| {
758            let light = DesignSystem::light();
759            s.color(light.text())
760                .font_size(light.font_size())
761                .background(light.bg_base())
762                .color(light.text())
763                .theme(light)
764        })
765        .apply_if(os_theme == winit::window::Theme::Dark, |s| {
766            let dark = DesignSystem::dark();
767            s.color(dark.text())
768                .font_size(dark.font_size())
769                .background(dark.bg_base())
770                .color(dark.text())
771                .theme(dark)
772        })
773        .line_height(1.2)
774        .class(LabelClass, |s| {
775            s.with_theme(|s, t| {
776                s.custom(|s: LabelCustomExprStyle| {
777                    s.selection_color(t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))))
778                })
779            })
780            .with::<Selectable>(|s, selectable| {
781                s.set_context_opt(
782                    Cursor,
783                    selectable.def(|selectable| {
784                        if selectable {
785                            Some(CursorStyle::Text)
786                        } else {
787                            None
788                        }
789                    }),
790                )
791            })
792            .focusable()
793        })
794        .class(ListClass, |s| {
795            s.apply(focus_style()).class(ListItemClass, |s| {
796                s.with_theme(|s, t| {
797                    s.hover(|s| s.background(t.bg_elevated())).selected(|s| {
798                        s.background(t.primary())
799                            .color(t.bg_base())
800                            .hover(|s| s.background(t.primary_muted()))
801                            .transition_background(Transition::linear(100.millis()))
802                    })
803                })
804                .with_theme(|s, t| s.border_radius(t.border_radius()).padding_left(t.padding()))
805                .items_center()
806            })
807        })
808        .class(CheckboxClass, |_| checkbox_style)
809        .class(LabeledCheckboxClass, |_| labeled_checkbox_style)
810        .class(RadioButtonClass, |_| radio_button_style)
811        .class(RadioButtonDotClass, |_| radio_button_dot_style)
812        .class(LabeledRadioButtonClass, |_| labeled_radio_button_style)
813        .class(TextInputClass, |_| input_style)
814        .class(ButtonClass, |_| button_style)
815        .class(TabSelectorClass, |_| tab_selector_style)
816        .custom_style_class(|s: scroll::ScrollCustomStyle| {
817            s.handle_border_radius(4.0)
818                .handle_rounded(false)
819                .apply_if(cfg!(target_os = "macos"), |s| s.handle_rounded(true))
820        })
821        .class(scroll::Handle, |s| {
822            s.with_theme(|s, t| {
823                s.background(t.border())
824                    .active(|s| s.background(t.text_muted()))
825                    .hover(|s| s.background(t.text_muted()))
826            })
827            .transition_background(Transition::ease_in_out(Duration::from_millis(300)))
828        })
829        .class(scroll::Track, |s| {
830            s.with_theme(|s, t| s.hover(|s| s.background(t.def(|t| t.border().with_alpha(0.3)))))
831                .background(css::TRANSPARENT)
832                .transition_background(Transition::ease_in_out(Duration::from_millis(300)))
833        })
834        .class(ToggleButtonClass, |_| toggle_button_style)
835        .class(SliderClass, |s| {
836            s.apply(focus_style())
837                .custom(|cs: SliderCustomStyle| {
838                    cs.bar_radius(100.pct())
839                        .accent_bar_radius(100.pct())
840                        .handle_radius(100.pct())
841                        .edge_align(true)
842                })
843                .with_theme(|s, t| {
844                    s.custom(|cs: SliderCustomExprStyle| {
845                        cs.bar_color(t.def(|t| Some(Brush::Solid(t.border()))))
846                            .accent_bar_color(t.def(|t| Brush::Solid(t.primary())))
847                            .handle_color(t.def(|t| Some(Brush::Solid(t.text()))))
848                    })
849                })
850        })
851        .class(PlaceholderTextClass, |s| {
852            s.with_theme(|s, t| {
853                s.color(t.text_muted()).disabled(|s| {
854                    s.color(t.def(|t| t.text_muted().with_alpha(0.5)))
855                        .set(Background, Some(Brush::Solid(css::BLACK)))
856                })
857            })
858        })
859        .class(TooltipClass, |s| s.apply(overlay_style()))
860        .class(dropdown::DropdownClass, move |s| {
861            s.padding(3)
862                .apply(focus_style())
863                .apply(border_style(true))
864                .selectable(false)
865                .class(dropdown::DropdownPreviewClass, |s| {
866                    s.gap(0.75.em()).class(SvgClass, |s| {
867                        s.with_theme(|s, t| {
868                            s.hover(|s| s.background(t.bg_elevated()))
869                                .border_radius(t.border_radius())
870                                .color(t.text())
871                        })
872                        .padding(5.)
873                        .size(1.em(), 1.em())
874                    })
875                })
876                .class(scroll::ScrollClass, move |s| {
877                    s.width_full()
878                        .scrollbar_width(0)
879                        .margin_top(3)
880                        .padding_vert(3)
881                        .apply(overlay_style())
882                        .items_center()
883                        .class(ListItemClass, move |s| {
884                            s.padding(6).with_theme(|s, t| {
885                                s.hover(|s| {
886                                    s.background(t.bg_elevated())
887                                        .selected(|s| s.background(t.primary_muted()))
888                                })
889                            })
890                        })
891                })
892        })
893        .class(ResizableClass, |s| s.padding_right(3))
894        .class(ResizableHandleClass, |s| {
895            s.custom(|cs: ResizableCustomStyle| cs.handle_thickness(3.))
896                .with_theme(|s, t| {
897                    s.custom(|cs: ResizableCustomExprStyle| {
898                        cs.handle_color(t.def(|t| Brush::Solid(t.primary_muted().with_alpha(0.5))))
899                            .hover(|s| s.handle_color(t.def(|t| Brush::Solid(t.primary()))))
900                    })
901                })
902        })
903        .class(HoverTargetClass, |s| {
904            s.with_theme(|s, t| {
905                s.padding(t.padding())
906                    .border_radius(t.border_radius())
907                    .background(t.bg_elevated())
908                    .outline(3)
909                    .file_hover(|s| s.background(t.bg_overlay()).outline_color(t.primary()))
910            })
911            .cursor(CursorStyle::Pointer)
912            .transition(Background, Transition::linear(100.millis()))
913        })
914}