1use crate::core::{Color, Position, Rect, Size, TextStyle};
15
16#[derive(Debug, Clone)]
18pub enum Gradient {
19 Linear {
21 start: Position,
22 end: Position,
23 stops: Vec<GradientStop>,
24 },
25 Radial {
27 center: Position,
28 radius: f32,
29 stops: Vec<GradientStop>,
30 },
31}
32
33#[derive(Debug, Clone, Copy)]
35pub struct GradientStop {
36 pub offset: f32,
37 pub color: Color,
38}
39
40impl GradientStop {
41 pub fn new(offset: f32, color: Color) -> Self {
42 Self { offset, color }
43 }
44}
45
46#[derive(Debug, Clone, Copy)]
48pub struct Shadow {
49 pub offset_x: f32,
50 pub offset_y: f32,
51 pub blur_radius: f32,
52 pub color: Color,
53}
54
55impl Shadow {
56 pub fn new(offset_x: f32, offset_y: f32, blur_radius: f32, color: Color) -> Self {
57 Self {
58 offset_x,
59 offset_y,
60 blur_radius,
61 color,
62 }
63 }
64}
65
66impl Default for Shadow {
67 fn default() -> Self {
68 Self {
69 offset_x: 2.0,
70 offset_y: 2.0,
71 blur_radius: 4.0,
72 color: Color::rgba(0.0, 0.0, 0.0, 0.3),
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
79pub struct ImageHandle {
80 pub id: u64,
82 pub width: u32,
84 pub height: u32,
85}
86
87impl ImageHandle {
88 pub fn new(id: u64, width: u32, height: u32) -> Self {
89 Self { id, width, height }
90 }
91}
92
93pub trait Painter {
99 fn fill_rect(&mut self, rect: Rect, color: Color, corner_radius: f32);
101
102 fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32, corner_radius: f32);
104
105 fn stroke_rounded_rect(
107 &mut self,
108 rect: Rect,
109 color: Color,
110 width: f32,
111 corner_radius: f32,
112 bg_color: Color,
113 ) {
114 self.stroke_rect(rect, color, width, corner_radius);
116 let _ = bg_color;
117 }
118
119 fn fill_circle(&mut self, center: Position, radius: f32, color: Color);
121
122 fn stroke_circle(&mut self, center: Position, radius: f32, color: Color, width: f32);
124
125 fn line(&mut self, from: Position, to: Position, color: Color, width: f32);
127
128 fn text(&mut self, pos: Position, text: &str, style: &TextStyle);
130
131 fn measure_text(&self, text: &str, style: &TextStyle) -> Size;
133
134 fn measure_text_mut(&mut self, text: &str, style: &TextStyle) -> Size {
136 self.measure_text(text, style)
137 }
138
139 fn push_clip(&mut self, rect: Rect);
141
142 fn pop_clip(&mut self);
144
145 fn fill_rect_gradient(&mut self, rect: Rect, gradient: &Gradient, corner_radius: f32) {
147 let color = match gradient {
149 Gradient::Linear { stops, .. } | Gradient::Radial { stops, .. } => {
150 stops.first().map(|s| s.color).unwrap_or(Color::WHITE)
151 }
152 };
153 self.fill_rect(rect, color, corner_radius);
154 }
155
156 fn shadow_rect(&mut self, rect: Rect, shadow: &Shadow, corner_radius: f32) {
158 let expand = shadow.blur_radius * 0.5;
160 let shadow_rect = Rect::new(
161 rect.x + shadow.offset_x - expand,
162 rect.y + shadow.offset_y - expand,
163 rect.width + expand * 2.0,
164 rect.height + expand * 2.0,
165 );
166 self.fill_rect(shadow_rect, shadow.color, corner_radius + expand);
167 }
168
169 fn draw_image(&mut self, _handle: &ImageHandle, _rect: Rect) {
171 }
173 fn draw_image_tinted(&mut self, handle: &ImageHandle, rect: Rect, _tint: Color) {
175 self.draw_image(handle, rect);
176 }
177}
178
179pub struct NullPainter;
184
185impl Painter for NullPainter {
186 fn fill_rect(&mut self, _rect: Rect, _color: Color, _corner_radius: f32) {}
187 fn stroke_rect(&mut self, _rect: Rect, _color: Color, _width: f32, _corner_radius: f32) {}
188 fn fill_circle(&mut self, _center: Position, _radius: f32, _color: Color) {}
189 fn stroke_circle(&mut self, _center: Position, _radius: f32, _color: Color, _width: f32) {}
190 fn line(&mut self, _from: Position, _to: Position, _color: Color, _width: f32) {}
191 fn text(&mut self, _pos: Position, _text: &str, _style: &TextStyle) {}
192 fn measure_text(&self, text: &str, style: &TextStyle) -> Size {
193 let w = style.font_size * 0.6 * text.len() as f32;
195 Size::new(w, style.font_size * 1.2)
196 }
197 fn push_clip(&mut self, _rect: Rect) {}
198 fn pop_clip(&mut self) {}
199 fn fill_rect_gradient(&mut self, _rect: Rect, _gradient: &Gradient, _corner_radius: f32) {}
200 fn shadow_rect(&mut self, _rect: Rect, _shadow: &Shadow, _corner_radius: f32) {}
201 fn draw_image(&mut self, _handle: &ImageHandle, _rect: Rect) {}
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn null_painter_measure_text() {
210 let np = NullPainter;
211 let style = TextStyle::default();
212 let size = np.measure_text("hello", &style);
213 assert!(size.width > 0.0);
214 assert!(size.height > 0.0);
215 }
216
217 #[test]
218 fn null_painter_measure_empty() {
219 let np = NullPainter;
220 let style = TextStyle::default();
221 let size = np.measure_text("", &style);
222 assert_eq!(size.width, 0.0);
223 assert!(size.height > 0.0);
224 }
225
226 #[test]
227 fn null_painter_operations_do_not_panic() {
228 let mut np = NullPainter;
229 np.fill_rect(Rect::ZERO, Color::RED, 0.0);
230 np.stroke_rect(Rect::ZERO, Color::RED, 1.0, 0.0);
231 np.fill_circle(Position::ZERO, 10.0, Color::GREEN);
232 np.stroke_circle(Position::ZERO, 10.0, Color::GREEN, 1.0);
233 np.line(
234 Position::ZERO,
235 Position::new(100.0, 100.0),
236 Color::BLUE,
237 2.0,
238 );
239 np.text(Position::ZERO, "test", &TextStyle::default());
240 np.push_clip(Rect::ZERO);
241 np.pop_clip();
242 }
243}