1use egui::{
9 Color32, CornerRadius, Painter, Rect, Response, Shadow, Shape, Stroke, StrokeKind,
10 epaint::RectShape,
11};
12
13pub type LabelVisuals = ButtonVisuals;
15
16#[derive(Copy, Clone, Debug, Default)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct ButtonVisuals {
20 pub text: Color32,
22 pub bg: Color32,
24 pub bg_hover: Color32,
26 pub bg_click: Color32,
28 pub bg_selected: Color32,
30 pub border: Stroke,
32 pub border_hover: Stroke,
34 pub border_click: Stroke,
36 pub corner_radius: CornerRadius,
37 pub shadow: Shadow,
38}
39
40impl PartialEq for ButtonVisuals {
41 fn eq(&self, other: &Self) -> bool {
42 self.text == other.text
43 && self.bg == other.bg
44 && self.bg_hover == other.bg_hover
45 && self.bg_click == other.bg_click
46 && self.bg_selected == other.bg_selected
47 && self.border == other.border
48 && self.border_hover == other.border_hover
49 && self.border_click == other.border_click
50 && self.corner_radius == other.corner_radius
51 && self.shadow == other.shadow
52 }
53}
54
55impl Eq for ButtonVisuals {}
56
57impl ButtonVisuals {
58 pub fn bg_from_res(&self, res: &Response) -> Color32 {
60 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
61 self.bg_click
62 } else if res.hovered() || res.highlighted() {
63 self.bg_hover
64 } else {
65 self.bg
66 }
67 }
68
69 pub fn border_from_res(&self, res: &Response) -> Stroke {
71 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
72 self.border_click
73 } else if res.hovered() || res.highlighted() {
74 self.border_hover
75 } else {
76 self.border
77 }
78 }
79}
80
81#[derive(Copy, Clone, Debug, Default)]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84pub struct TextEditVisuals {
85 pub text: Color32,
87 pub bg: Color32,
89 pub border: Stroke,
91 pub border_hover: Stroke,
93 pub border_open: Stroke,
95 pub corner_radius: CornerRadius,
96 pub shadow: Shadow,
97}
98
99impl PartialEq for TextEditVisuals {
100 fn eq(&self, other: &Self) -> bool {
101 self.text == other.text
102 && self.bg == other.bg
103 && self.border == other.border
104 && self.border_hover == other.border_hover
105 && self.border_open == other.border_open
106 && self.corner_radius == other.corner_radius
107 && self.shadow == other.shadow
108 }
109}
110
111impl Eq for TextEditVisuals {}
112
113impl TextEditVisuals {
114 pub fn border_from_res(&self, res: &Response) -> Stroke {
116 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
117 self.border_open
118 } else if res.hovered() || res.highlighted() {
119 self.border_hover
120 } else {
121 self.border
122 }
123 }
124}
125
126#[derive(Copy, Clone, Debug, Default)]
128#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
129pub struct ComboBoxVisuals {
130 pub bg: Color32,
132 pub icon: Color32,
134 pub bg_hover: Color32,
136 pub bg_open: Color32,
138 pub border: Stroke,
140 pub border_hover: Stroke,
142 pub border_open: Stroke,
144 pub corner_radius: CornerRadius,
145 pub shadow: Shadow,
146}
147
148impl PartialEq for ComboBoxVisuals {
149 fn eq(&self, other: &Self) -> bool {
150 self.bg == other.bg
151 && self.icon == other.icon
152 && self.bg_hover == other.bg_hover
153 && self.bg_open == other.bg_open
154 && self.border == other.border
155 && self.border_hover == other.border_hover
156 && self.border_open == other.border_open
157 && self.corner_radius == other.corner_radius
158 && self.shadow == other.shadow
159 }
160}
161
162impl Eq for ComboBoxVisuals {}
163
164impl ComboBoxVisuals {
165 pub fn bg_from_res(&self, res: &Response) -> Color32 {
167 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
168 self.bg_open
169 } else if res.hovered() || res.highlighted() {
170 self.bg_hover
171 } else {
172 self.bg
173 }
174 }
175
176 pub fn border_from_res(&self, res: &Response) -> Stroke {
178 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
179 self.border_open
180 } else if res.hovered() || res.highlighted() {
181 self.border_hover
182 } else {
183 self.border
184 }
185 }
186}
187
188#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
190#[derive(Clone, Copy, Debug, Default)]
191pub struct FrameVisuals {
192 pub bg: Color32,
193 pub bg_hover: Color32,
194 pub bg_click: Color32,
195 pub bg_selected: Color32,
197 pub border: Stroke,
198 pub border_hover: Stroke,
199 pub border_click: Stroke,
200 pub corner_radius: CornerRadius,
201 pub shadow: Shadow,
202}
203
204impl PartialEq for FrameVisuals {
205 fn eq(&self, other: &Self) -> bool {
206 self.bg == other.bg
207 && self.bg_hover == other.bg_hover
208 && self.bg_click == other.bg_click
209 && self.bg_selected == other.bg_selected
210 && self.border == other.border
211 && self.border_hover == other.border_hover
212 && self.border_click == other.border_click
213 && self.corner_radius == other.corner_radius
214 && self.shadow == other.shadow
215 }
216}
217
218impl Eq for FrameVisuals {}
219
220impl FrameVisuals {
221 pub fn bg_from_res(&self, res: &Response) -> Color32 {
222 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
223 self.bg_click
224 } else if res.contains_pointer() || res.hovered() || res.highlighted() {
225 self.bg_hover
226 } else {
227 self.bg
228 }
229 }
230
231 pub fn border_from_res(&self, res: &Response) -> Stroke {
232 if res.is_pointer_button_down_on() || res.has_focus() || res.clicked() {
233 self.border_click
234 } else if res.contains_pointer() || res.hovered() || res.highlighted() {
235 self.border_hover
236 } else {
237 self.border
238 }
239 }
240
241 pub fn paint_at(&self, painter: &Painter, rect: Rect, fill: Color32, stroke: Stroke) {
243 painter.add(paint_shape(
244 rect,
245 fill,
246 stroke,
247 self.corner_radius,
248 self.shadow,
249 ));
250 }
251}
252
253pub(crate) fn paint_shape(
254 rect: Rect,
255 fill: Color32,
256 stroke: Stroke,
257 corner_radius: CornerRadius,
258 shadow: Shadow,
259) -> Shape {
260 if rect.width() <= 0.0 || rect.height() <= 0.0 {
261 return Shape::Noop;
262 }
263
264 let has_fill = fill != Color32::TRANSPARENT;
265
266 let has_shadow = shadow != Shadow::NONE && shadow.color != Color32::TRANSPARENT;
267 if !has_fill && stroke.is_empty() && !has_shadow {
268 return Shape::Noop;
269 }
270
271 let mut shapes = Vec::new();
272
273 if has_shadow {
274 shapes.push(Shape::from(shadow.as_shape(rect, corner_radius)));
275 }
276
277 if has_fill || !stroke.is_empty() {
278 shapes.push(Shape::Rect(RectShape::new(
279 rect,
280 corner_radius,
281 fill,
282 stroke,
283 StrokeKind::Inside,
284 )));
285 }
286
287 match shapes.len() {
288 0 => Shape::Noop,
289 1 => shapes.remove(0),
290 _ => Shape::Vec(shapes),
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn paint_shape_draws_fill_without_stroke() {
300 let shape = paint_shape(
301 Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(10.0, 10.0)),
302 Color32::RED,
303 Stroke::NONE,
304 CornerRadius::ZERO,
305 Shadow::NONE,
306 );
307 match shape {
308 Shape::Rect(rect) => assert_eq!(rect.fill, Color32::RED),
309 other => panic!("expected filled rect, got {other:?}"),
310 }
311 }
312}