1use std::rc::Rc;
2
3use crate::{
4 Brush, Color, ImageAlignment, ImageFilter, ImageFit, ImageHandle, ImageSourceRect, Px, Rect,
5 Scene, SceneNode,
6};
7
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub struct ControlVisualState {
10 pub alpha: f32,
11 pub enabled: bool,
12 pub hovered: bool,
13 pub pressed: bool,
14 pub dragged: bool,
15 pub focused: bool,
16}
17
18impl Default for ControlVisualState {
19 fn default() -> Self {
20 Self {
21 alpha: 1.0,
22 enabled: true,
23 hovered: false,
24 pressed: false,
25 dragged: false,
26 focused: false,
27 }
28 }
29}
30
31pub type ControlPainter = Rc<dyn Fn(&mut Scene, Rect, ControlVisualState)>;
32
33#[derive(Clone)]
34#[non_exhaustive]
35pub enum ControlVisual {
36 Rect {
37 brush: Brush,
38 radius: [Px; 4],
39 },
40 Image {
41 handle: ImageHandle,
42 source_rect: Option<ImageSourceRect>,
43 tint: Color,
44 fit: ImageFit,
45 filter: ImageFilter,
46 alignment: ImageAlignment,
47 },
48 Custom(ControlPainter),
49}
50
51impl ControlVisual {
52 pub fn rect(brush: Brush, radius: [Px; 4]) -> Self {
53 Self::Rect { brush, radius }
54 }
55
56 pub fn image(handle: ImageHandle, source_rect: Option<ImageSourceRect>, tint: Color) -> Self {
57 Self::Image {
58 handle,
59 source_rect,
60 tint,
61 fit: ImageFit::FillBounds,
62 filter: ImageFilter::Linear,
63 alignment: ImageAlignment::Center,
64 }
65 }
66
67 pub fn custom(painter: impl Fn(&mut Scene, Rect, ControlVisualState) + 'static) -> Self {
68 Self::Custom(Rc::new(painter))
69 }
70
71 pub fn image_fit(mut self, fit: ImageFit) -> Self {
72 if let Self::Image { fit: current, .. } = &mut self {
73 *current = fit;
74 }
75 self
76 }
77
78 pub fn image_filter(mut self, filter: ImageFilter) -> Self {
79 if let Self::Image {
80 filter: current, ..
81 } = &mut self
82 {
83 *current = filter;
84 }
85 self
86 }
87
88 pub fn image_alignment(mut self, alignment: ImageAlignment) -> Self {
89 if let Self::Image {
90 alignment: current, ..
91 } = &mut self
92 {
93 *current = alignment;
94 }
95 self
96 }
97
98 pub fn paint(&self, scene: &mut Scene, rect: Rect, state: ControlVisualState) {
99 match self {
100 Self::Rect { brush, radius } => scene.nodes.push(SceneNode::Rect {
101 rect,
102 brush: scale_brush_alpha(*brush, state.alpha),
103 radius: *radius,
104 }),
105 Self::Image {
106 handle,
107 source_rect,
108 tint,
109 fit,
110 filter,
111 alignment,
112 } => scene.nodes.push(SceneNode::Image {
113 rect,
114 handle: *handle,
115 tint: scale_color_alpha(*tint, state.alpha),
116 fit: *fit,
117 filter: *filter,
118 source_rect: *source_rect,
119 alignment: *alignment,
120 }),
121 Self::Custom(painter) => painter(scene, rect, state),
122 }
123 }
124
125 pub fn custom_identity(&self) -> Option<usize> {
126 match self {
127 Self::Custom(painter) => Some(Rc::as_ptr(painter) as *const () as usize),
128 _ => None,
129 }
130 }
131}
132
133impl std::fmt::Debug for ControlVisual {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 match self {
136 Self::Rect { brush, radius } => f
137 .debug_struct("Rect")
138 .field("brush", brush)
139 .field("radius", radius)
140 .finish(),
141 Self::Image {
142 handle,
143 source_rect,
144 tint,
145 fit,
146 filter,
147 alignment,
148 } => f
149 .debug_struct("Image")
150 .field("handle", handle)
151 .field("source_rect", source_rect)
152 .field("tint", tint)
153 .field("fit", fit)
154 .field("filter", filter)
155 .field("alignment", alignment)
156 .finish(),
157 Self::Custom(_) => f
158 .debug_struct("Custom")
159 .field("identity", &self.custom_identity())
160 .finish(),
161 }
162 }
163}
164
165impl PartialEq for ControlVisual {
166 fn eq(&self, other: &Self) -> bool {
167 match (self, other) {
168 (
169 Self::Rect {
170 brush: a,
171 radius: ra,
172 },
173 Self::Rect {
174 brush: b,
175 radius: rb,
176 },
177 ) => a == b && ra == rb,
178 (
179 Self::Image {
180 handle: ah,
181 source_rect: ar,
182 tint: at,
183 fit: af,
184 filter: ax,
185 alignment: aa,
186 },
187 Self::Image {
188 handle: bh,
189 source_rect: br,
190 tint: bt,
191 fit: bf,
192 filter: bx,
193 alignment: ba,
194 },
195 ) => ah == bh && ar == br && at == bt && af == bf && ax == bx && aa == ba,
196 (Self::Custom(a), Self::Custom(b)) => Rc::ptr_eq(a, b),
197 _ => false,
198 }
199 }
200}
201
202#[derive(Clone, Debug, Default, PartialEq)]
203pub struct ControlVisualSet {
204 pub normal: Option<ControlVisual>,
205 pub hovered: Option<ControlVisual>,
206 pub pressed: Option<ControlVisual>,
207 pub dragged: Option<ControlVisual>,
208 pub focused: Option<ControlVisual>,
209 pub disabled: Option<ControlVisual>,
210}
211
212impl ControlVisualSet {
213 pub fn resolve(&self, state: ControlVisualState) -> Option<&ControlVisual> {
214 let selected = if !state.enabled {
215 self.disabled.as_ref()
216 } else if state.dragged {
217 self.dragged.as_ref()
218 } else if state.pressed {
219 self.pressed.as_ref()
220 } else if state.focused {
221 self.focused.as_ref()
222 } else if state.hovered {
223 self.hovered.as_ref()
224 } else {
225 None
226 };
227 selected.or(self.normal.as_ref())
228 }
229
230 pub fn with_normal(mut self, visual: ControlVisual) -> Self {
231 self.normal = Some(visual);
232 self
233 }
234
235 pub fn with_hovered(mut self, visual: ControlVisual) -> Self {
236 self.hovered = Some(visual);
237 self
238 }
239
240 pub fn with_pressed(mut self, visual: ControlVisual) -> Self {
241 self.pressed = Some(visual);
242 self
243 }
244
245 pub fn with_dragged(mut self, visual: ControlVisual) -> Self {
246 self.dragged = Some(visual);
247 self
248 }
249
250 pub fn with_focused(mut self, visual: ControlVisual) -> Self {
251 self.focused = Some(visual);
252 self
253 }
254
255 pub fn with_disabled(mut self, visual: ControlVisual) -> Self {
256 self.disabled = Some(visual);
257 self
258 }
259}
260
261fn scale_color_alpha(color: Color, alpha: f32) -> Color {
262 Color(
263 color.0,
264 color.1,
265 color.2,
266 (color.3 as f32 * alpha.clamp(0.0, 1.0)) as u8,
267 )
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273
274 #[test]
275 fn control_visual_state_priority_is_stable() {
276 let set = ControlVisualSet {
277 normal: Some(ControlVisual::image(1, None, Color::WHITE)),
278 hovered: Some(ControlVisual::image(2, None, Color::WHITE)),
279 focused: Some(ControlVisual::image(3, None, Color::WHITE)),
280 dragged: Some(ControlVisual::image(4, None, Color::WHITE)),
281 disabled: Some(ControlVisual::image(5, None, Color::WHITE)),
282 ..Default::default()
283 };
284 let focused_hovered = ControlVisualState {
285 hovered: true,
286 focused: true,
287 ..Default::default()
288 };
289 assert!(matches!(
290 set.resolve(focused_hovered),
291 Some(ControlVisual::Image { handle: 3, .. })
292 ));
293 let disabled_dragged = ControlVisualState {
294 enabled: false,
295 dragged: true,
296 ..Default::default()
297 };
298 assert!(matches!(
299 set.resolve(disabled_dragged),
300 Some(ControlVisual::Image { handle: 5, .. })
301 ));
302 }
303}
304
305fn scale_brush_alpha(brush: Brush, alpha: f32) -> Brush {
306 match brush {
307 Brush::Solid(color) => Brush::Solid(scale_color_alpha(color, alpha)),
308 Brush::Linear {
309 start,
310 end,
311 start_color,
312 end_color,
313 } => Brush::Linear {
314 start,
315 end,
316 start_color: scale_color_alpha(start_color, alpha),
317 end_color: scale_color_alpha(end_color, alpha),
318 },
319 Brush::LinearNormalized {
320 start,
321 end,
322 start_color,
323 end_color,
324 } => Brush::LinearNormalized {
325 start,
326 end,
327 start_color: scale_color_alpha(start_color, alpha),
328 end_color: scale_color_alpha(end_color, alpha),
329 },
330 Brush::Radial {
331 center,
332 radius,
333 start_color,
334 end_color,
335 } => Brush::Radial {
336 center,
337 radius,
338 start_color: scale_color_alpha(start_color, alpha),
339 end_color: scale_color_alpha(end_color, alpha),
340 },
341 Brush::Sweep {
342 center,
343 start_color,
344 end_color,
345 } => Brush::Sweep {
346 center,
347 start_color: scale_color_alpha(start_color, alpha),
348 end_color: scale_color_alpha(end_color, alpha),
349 },
350 }
351}