Skip to main content

egui_shadcn/
theme.rs

1use crate::tokens::{
2    ColorPalette, ControlSize, ControlVariant, DEFAULT_FOCUS, DEFAULT_MOTION, DEFAULT_RADIUS,
3    FocusTokens, InputTokens, InputVariant, MotionTokens, RadiusScale, StateColors, VariantTokens,
4    input_tokens, variant_tokens,
5};
6use egui::style::{WidgetVisuals, Widgets};
7use egui::{Color32, CornerRadius, FontId, Stroke, Ui, Vec2};
8use log::{info, trace};
9
10#[derive(Clone, Debug)]
11pub struct ControlVisuals {
12    pub widgets: Widgets,
13    pub padding: Vec2,
14    pub text_style: FontId,
15    pub expansion: f32,
16}
17
18#[derive(Clone, Debug)]
19pub struct InputVisuals {
20    pub widgets: Widgets,
21    pub padding: Vec2,
22    pub text_style: FontId,
23    pub focus_stroke: Stroke,
24    pub invalid_stroke: Stroke,
25    pub selection_bg: Color32,
26    pub selection_fg: Color32,
27    pub placeholder: Color32,
28    pub text_color: Color32,
29    pub rounding: CornerRadius,
30    pub expansion: f32,
31}
32
33#[derive(Clone, Debug)]
34pub struct Theme {
35    pub palette: ColorPalette,
36    pub motion: MotionTokens,
37    pub radius: RadiusScale,
38    pub focus: FocusTokens,
39}
40
41impl Theme {
42    pub fn new(palette: ColorPalette) -> Self {
43        info!("Initializing egui-shadcn theme");
44        Self {
45            palette,
46            motion: DEFAULT_MOTION,
47            radius: DEFAULT_RADIUS,
48            focus: DEFAULT_FOCUS,
49        }
50    }
51
52    pub fn with_tokens(
53        palette: ColorPalette,
54        motion: MotionTokens,
55        radius: RadiusScale,
56        focus: FocusTokens,
57    ) -> Self {
58        info!("Initializing egui-shadcn theme with custom tokens");
59        Self {
60            palette,
61            motion,
62            radius,
63            focus,
64        }
65    }
66
67    pub fn control(&self, variant: ControlVariant, size: ControlSize) -> ControlVisuals {
68        trace!("Building style for variant {:?} size {:?}", variant, size);
69        let tokens = variant_tokens(&self.palette, variant);
70        let rounding = size.rounding_with_scale(&self.radius);
71        let expansion = size.expansion();
72        ControlVisuals {
73            widgets: widgets_from_variant(&tokens, rounding, expansion),
74            padding: size.padding(),
75            text_style: size.font(),
76            expansion,
77        }
78    }
79
80    pub fn input(&self, size: ControlSize) -> InputVisuals {
81        self.input_variant(size, InputVariant::Surface)
82    }
83
84    pub fn input_variant(&self, size: ControlSize, variant: InputVariant) -> InputVisuals {
85        trace!(
86            "Building style for input size {:?} variant {:?}",
87            size, variant
88        );
89        let tokens = input_tokens(&self.palette, variant);
90        let rounding = size.rounding_with_scale(&self.radius);
91        let expansion = size.expansion();
92        InputVisuals {
93            widgets: widgets_from_input(&tokens, rounding, expansion),
94            padding: size.padding(),
95            text_style: size.font(),
96            focus_stroke: tokens.focused.border,
97            invalid_stroke: tokens.invalid.border,
98            selection_bg: tokens.selection_bg,
99            selection_fg: tokens.selection_fg,
100            placeholder: tokens.placeholder,
101            text_color: tokens.idle.fg_stroke.color,
102            rounding,
103            expansion,
104        }
105    }
106
107    pub fn scoped<R>(&self, ui: &mut Ui, widgets: Widgets, inner: impl FnOnce(&mut Ui) -> R) -> R {
108        ui.scope(|scope_ui| {
109            let mut style = scope_ui.style().as_ref().clone();
110            style.visuals.widgets = widgets;
111            scope_ui.set_style(style);
112            inner(scope_ui)
113        })
114        .inner
115    }
116}
117
118impl Default for Theme {
119    fn default() -> Self {
120        Self::new(ColorPalette::default())
121    }
122}
123
124pub(crate) fn widget_visuals(
125    state: &StateColors,
126    corner_radius: CornerRadius,
127    expansion: f32,
128) -> WidgetVisuals {
129    WidgetVisuals {
130        bg_fill: state.bg_fill,
131        weak_bg_fill: state.bg_fill,
132        bg_stroke: state.border,
133        fg_stroke: state.fg_stroke,
134        corner_radius,
135        expansion,
136    }
137}
138
139pub(crate) fn widgets_from_variant(
140    tokens: &VariantTokens,
141    corner_radius: CornerRadius,
142    expansion: f32,
143) -> Widgets {
144    Widgets {
145        noninteractive: widget_visuals(&tokens.disabled, corner_radius, expansion),
146        inactive: widget_visuals(&tokens.idle, corner_radius, expansion),
147        hovered: widget_visuals(&tokens.hovered, corner_radius, expansion),
148        active: widget_visuals(&tokens.active, corner_radius, expansion),
149        open: widget_visuals(&tokens.hovered, corner_radius, expansion),
150    }
151}
152
153pub(crate) fn widgets_from_input(
154    tokens: &InputTokens,
155    corner_radius: CornerRadius,
156    expansion: f32,
157) -> Widgets {
158    Widgets {
159        noninteractive: widget_visuals(&tokens.disabled, corner_radius, expansion),
160        inactive: widget_visuals(&tokens.idle, corner_radius, expansion),
161        hovered: widget_visuals(&tokens.hovered, corner_radius, expansion),
162        active: widget_visuals(&tokens.focused, corner_radius, expansion),
163        open: widget_visuals(&tokens.hovered, corner_radius, expansion),
164    }
165}