1use std::{borrow::Cow, fmt};
2
3use emath::Vec2;
4use epaint::{Color32, FontId, Shadow, Stroke, text::TextWrapMode};
5use smallvec::SmallVec;
6
7use crate::{
8 Frame, Response, Style, TextBuffer as _, TextStyle,
9 style::{WidgetVisuals, Widgets},
10};
11
12pub struct TextVisuals {
14 pub font_id: FontId,
16
17 pub color: Color32,
19
20 pub underline: Stroke,
22 pub strikethrough: Stroke,
23}
24
25pub struct WidgetStyle {
27 pub frame: Frame,
28
29 pub text: TextVisuals,
30
31 pub stroke: Stroke,
32}
33
34pub struct ButtonStyle {
36 pub frame: Frame,
37 pub text_style: TextVisuals,
38}
39
40pub struct CheckboxStyle {
42 pub frame: Frame,
44
45 pub text_style: TextVisuals,
47
48 pub checkbox_size: f32,
50
51 pub check_size: f32,
53
54 pub checkbox_frame: Frame,
56
57 pub check_stroke: Stroke,
59}
60
61pub struct LabelStyle {
63 pub frame: Frame,
65
66 pub text: TextVisuals,
68
69 pub wrap_mode: TextWrapMode,
71}
72
73pub struct SeparatorStyle {
75 pub spacing: f32,
77
78 pub stroke: Stroke,
80}
81
82#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
84pub enum WidgetState {
85 Noninteractive,
86 #[default]
87 Inactive,
88 Hovered,
89 Active,
90}
91
92impl Widgets {
93 pub fn state(&self, state: WidgetState) -> &WidgetVisuals {
95 match state {
96 WidgetState::Noninteractive => &self.noninteractive,
97 WidgetState::Inactive => &self.inactive,
98 WidgetState::Hovered => &self.hovered,
99 WidgetState::Active => &self.active,
100 }
101 }
102}
103
104impl Response {
105 pub fn widget_state(&self) -> WidgetState {
106 if !self.sense.interactive() {
107 WidgetState::Noninteractive
108 } else if self.is_pointer_button_down_on() || self.has_focus() || self.clicked() {
109 WidgetState::Active
110 } else if self.hovered() || self.highlighted() {
111 WidgetState::Hovered
112 } else {
113 WidgetState::Inactive
114 }
115 }
116}
117
118impl Style {
119 pub fn widget_style(&self, _classes: &Classes, state: WidgetState) -> WidgetStyle {
121 let visuals = self.visuals.widgets.state(state);
122 let font_id = self.override_font_id.clone();
123 WidgetStyle {
124 frame: Frame {
125 fill: visuals.bg_fill,
126 stroke: visuals.bg_stroke,
127 corner_radius: visuals.corner_radius,
128 inner_margin: self.spacing.button_padding.into(),
129 ..Default::default()
130 },
131 stroke: visuals.fg_stroke,
132 text: TextVisuals {
133 color: self
134 .visuals
135 .override_text_color
136 .unwrap_or_else(|| visuals.text_color()),
137 font_id: font_id.unwrap_or_else(|| TextStyle::Body.resolve(self)),
138 strikethrough: Stroke::NONE,
139 underline: Stroke::NONE,
140 },
141 }
142 }
143
144 pub fn button_style(&self, classes: &Classes, state: WidgetState) -> ButtonStyle {
147 let mut visuals = *self.visuals.widgets.state(state);
148 let mut ws = self.widget_style(classes, state);
149
150 if classes.has(SELECTED_CLASS) {
151 visuals.weak_bg_fill = self.visuals.selection.bg_fill;
152 visuals.bg_fill = self.visuals.selection.bg_fill;
153 visuals.fg_stroke = self.visuals.selection.stroke;
154 ws.text.color = self.visuals.selection.stroke.color;
155 }
156
157 ButtonStyle {
158 frame: Frame {
159 fill: visuals.weak_bg_fill,
160 stroke: visuals.bg_stroke,
161 corner_radius: visuals.corner_radius,
162 outer_margin: (-Vec2::splat(visuals.expansion)).into(),
163 inner_margin: (self.spacing.button_padding + Vec2::splat(visuals.expansion)
164 - Vec2::splat(visuals.bg_stroke.width))
165 .into(),
166 ..Default::default()
167 },
168 text_style: ws.text,
169 }
170 }
171
172 pub fn checkbox_style(&self, classes: &Classes, state: WidgetState) -> CheckboxStyle {
175 let visuals = self.visuals.widgets.state(state);
176 let ws = self.widget_style(classes, state);
177 CheckboxStyle {
178 frame: Frame::new(),
179 checkbox_size: self.spacing.icon_width,
180 check_size: self.spacing.icon_width_inner,
181 checkbox_frame: Frame {
182 fill: visuals.bg_fill,
183 corner_radius: visuals.corner_radius,
184 stroke: visuals.bg_stroke,
185 ..Default::default()
186 },
187 text_style: ws.text,
188 check_stroke: ws.stroke,
189 }
190 }
191
192 pub fn label_style(&self, classes: &Classes, state: WidgetState) -> LabelStyle {
195 let ws = self.widget_style(classes, state);
196 LabelStyle {
197 frame: Frame {
198 fill: ws.frame.fill,
199 inner_margin: 0.0.into(),
200 outer_margin: 0.0.into(),
201 stroke: Stroke::NONE,
202 shadow: Shadow::NONE,
203 corner_radius: 0.into(),
204 },
205 text: ws.text,
206 wrap_mode: TextWrapMode::Wrap,
207 }
208 }
209
210 pub fn separator_style(&self, _classes: &Classes, _state: WidgetState) -> SeparatorStyle {
213 let visuals = self.visuals.noninteractive();
214 SeparatorStyle {
215 spacing: 6.0,
216 stroke: visuals.bg_stroke,
217 }
218 }
219}
220
221pub const ROOT_CLASS: &str = "root";
223
224pub const SELECTED_CLASS: &str = "selected";
226
227pub type ClassName = Cow<'static, str>;
229
230#[derive(Debug, Default, Clone)]
235pub struct Classes {
236 classes: SmallVec<[ClassName; 5]>,
237}
238
239impl Classes {
240 #[inline]
242 fn add_if(&mut self, class: impl Into<ClassName>, condition: bool) {
243 if condition {
244 self.classes.push(class.into());
245 }
246 }
247}
248
249impl HasClasses for Classes {
250 fn classes(&self) -> &Classes {
251 self
252 }
253
254 fn classes_mut(&mut self) -> &mut Classes {
255 self
256 }
257}
258
259impl core::fmt::Display for Classes {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 self.classes.iter().for_each(|class| {
262 let _ = f.write_str(class.as_str());
263 });
264 f.write_str("")
265 }
266}
267
268pub trait HasClasses {
270 fn classes(&self) -> &Classes;
271
272 fn classes_mut(&mut self) -> &mut Classes;
273
274 #[inline]
276 fn with_class(mut self, class: impl Into<ClassName>) -> Self
277 where
278 Self: Sized,
279 {
280 self.classes_mut().add_if(class.into(), true);
281 self
282 }
283
284 #[inline]
286 fn with_class_if(mut self, class: impl Into<ClassName>, condition: bool) -> Self
287 where
288 Self: Sized,
289 {
290 self.classes_mut().add_if(class.into(), condition);
291 self
292 }
293
294 #[inline]
296 fn add_class(&mut self, class: impl Into<ClassName>) -> &mut Self
297 where
298 Self: Sized,
299 {
300 self.classes_mut().add_if(class.into(), true);
301 self
302 }
303
304 #[inline]
306 fn add_class_if(&mut self, class: impl Into<ClassName>, condition: bool) -> &mut Self
307 where
308 Self: Sized,
309 {
310 self.classes_mut().add_if(class.into(), condition);
311 self
312 }
313
314 fn has(&self, class: impl Into<ClassName>) -> bool {
316 self.classes().classes.contains(&class.into())
317 }
318}