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}