1use crate::geom::{Point, ScreenPoint, ScreenRect};
7use crate::transform::Transform;
8use crate::view::Viewport;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct Color {
15 pub r: f32,
17 pub g: f32,
19 pub b: f32,
21 pub a: f32,
23}
24
25impl Color {
26 pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
28 Self { r, g, b, a }
29 }
30
31 pub const BLACK: Self = Self::new(0.0, 0.0, 0.0, 1.0);
33 pub const WHITE: Self = Self::new(1.0, 1.0, 1.0, 1.0);
35}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct LineStyle {
42 pub color: Color,
44 pub width: f32,
46}
47
48impl Default for LineStyle {
49 fn default() -> Self {
50 Self {
51 color: Color::BLACK,
52 width: 1.0,
53 }
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum MarkerShape {
60 Circle,
62 Square,
64 Cross,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
72pub struct MarkerStyle {
73 pub color: Color,
75 pub size: f32,
77 pub shape: MarkerShape,
79}
80
81impl Default for MarkerStyle {
82 fn default() -> Self {
83 Self {
84 color: Color::BLACK,
85 size: 4.0,
86 shape: MarkerShape::Circle,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq)]
93pub(crate) struct RectStyle {
94 pub fill: Color,
96 pub stroke: Color,
98 pub stroke_width: f32,
100}
101
102impl Default for RectStyle {
103 fn default() -> Self {
104 Self {
105 fill: Color::new(0.0, 0.0, 0.0, 0.0),
106 stroke: Color::BLACK,
107 stroke_width: 1.0,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq)]
114pub(crate) struct TextStyle {
115 pub color: Color,
117 pub size: f32,
119}
120
121impl Default for TextStyle {
122 fn default() -> Self {
123 Self {
124 color: Color::BLACK,
125 size: 12.0,
126 }
127 }
128}
129
130#[derive(Debug, Clone, Copy, PartialEq)]
132pub(crate) struct LineSegment {
133 pub start: ScreenPoint,
135 pub end: ScreenPoint,
137}
138
139impl LineSegment {
140 pub(crate) fn new(start: ScreenPoint, end: ScreenPoint) -> Self {
142 Self { start, end }
143 }
144}
145
146#[derive(Debug, Clone)]
148pub(crate) enum RenderCommand {
149 ClipRect(ScreenRect),
151 ClipEnd,
153 LineSegments {
155 segments: Vec<LineSegment>,
157 style: LineStyle,
159 },
160 Points {
162 points: Vec<ScreenPoint>,
164 style: MarkerStyle,
166 },
167 Rect {
169 rect: ScreenRect,
171 style: RectStyle,
173 },
174 Text {
176 position: ScreenPoint,
178 text: String,
180 style: TextStyle,
182 },
183}
184
185#[derive(Debug, Default, Clone)]
187pub(crate) struct RenderList {
188 commands: Vec<RenderCommand>,
189}
190
191impl RenderList {
192 pub(crate) fn new() -> Self {
194 Self::default()
195 }
196
197 pub(crate) fn push(&mut self, command: RenderCommand) {
199 self.commands.push(command);
200 }
201
202 pub(crate) fn commands(&self) -> &[RenderCommand] {
204 &self.commands
205 }
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub(crate) struct RenderCacheKey {
211 pub viewport: Viewport,
213 pub size: (u32, u32),
215 pub generation: u64,
217}
218
219pub(crate) fn build_line_segments(
221 points: &[Point],
222 transform: &Transform,
223 clip: ScreenRect,
224 out: &mut Vec<LineSegment>,
225) {
226 out.clear();
227 if points.len() < 2 {
228 return;
229 }
230 for window in points.windows(2) {
231 let Some(start) = transform.data_to_screen(window[0]) else {
232 continue;
233 };
234 let Some(end) = transform.data_to_screen(window[1]) else {
235 continue;
236 };
237 if let Some((clipped_start, clipped_end)) = clip_segment(start, end, clip) {
238 out.push(LineSegment::new(clipped_start, clipped_end));
239 }
240 }
241}
242
243pub(crate) fn build_scatter_points(
245 points: &[Point],
246 transform: &Transform,
247 clip: ScreenRect,
248 out: &mut Vec<ScreenPoint>,
249) {
250 out.clear();
251 for point in points {
252 let Some(screen) = transform.data_to_screen(*point) else {
253 continue;
254 };
255 if screen.x >= clip.min.x
256 && screen.x <= clip.max.x
257 && screen.y >= clip.min.y
258 && screen.y <= clip.max.y
259 {
260 out.push(screen);
261 }
262 }
263}
264
265fn clip_segment(
266 mut start: ScreenPoint,
267 mut end: ScreenPoint,
268 rect: ScreenRect,
269) -> Option<(ScreenPoint, ScreenPoint)> {
270 const LEFT: u8 = 1;
271 const RIGHT: u8 = 2;
272 const TOP: u8 = 4;
273 const BOTTOM: u8 = 8;
274
275 let mut out_start = region_code(start, rect, LEFT, RIGHT, TOP, BOTTOM);
276 let mut out_end = region_code(end, rect, LEFT, RIGHT, TOP, BOTTOM);
277
278 loop {
279 if (out_start | out_end) == 0 {
280 return Some((start, end));
281 }
282 if (out_start & out_end) != 0 {
283 return None;
284 }
285
286 let out_code = if out_start != 0 { out_start } else { out_end };
287 let (mut x, mut y) = (0.0_f32, 0.0_f32);
288
289 if (out_code & TOP) != 0 {
290 x = start.x + (end.x - start.x) * (rect.min.y - start.y) / (end.y - start.y);
291 y = rect.min.y;
292 } else if (out_code & BOTTOM) != 0 {
293 x = start.x + (end.x - start.x) * (rect.max.y - start.y) / (end.y - start.y);
294 y = rect.max.y;
295 } else if (out_code & RIGHT) != 0 {
296 y = start.y + (end.y - start.y) * (rect.max.x - start.x) / (end.x - start.x);
297 x = rect.max.x;
298 } else if (out_code & LEFT) != 0 {
299 y = start.y + (end.y - start.y) * (rect.min.x - start.x) / (end.x - start.x);
300 x = rect.min.x;
301 }
302
303 let new_point = ScreenPoint::new(x, y);
304 if out_code == out_start {
305 start = new_point;
306 out_start = region_code(start, rect, LEFT, RIGHT, TOP, BOTTOM);
307 } else {
308 end = new_point;
309 out_end = region_code(end, rect, LEFT, RIGHT, TOP, BOTTOM);
310 }
311 }
312}
313
314fn region_code(
315 point: ScreenPoint,
316 rect: ScreenRect,
317 left: u8,
318 right: u8,
319 top: u8,
320 bottom: u8,
321) -> u8 {
322 let mut code = 0;
323 if point.x < rect.min.x {
324 code |= left;
325 } else if point.x > rect.max.x {
326 code |= right;
327 }
328 if point.y < rect.min.y {
329 code |= top;
330 } else if point.y > rect.max.y {
331 code |= bottom;
332 }
333 code
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339 use crate::geom::Point;
340 use crate::view::Range;
341 use crate::view::Viewport;
342
343 #[test]
344 fn clip_segment_inside() {
345 let rect = ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0));
346 let start = ScreenPoint::new(2.0, 2.0);
347 let end = ScreenPoint::new(8.0, 8.0);
348 let clipped = clip_segment(start, end, rect).expect("segment should clip");
349 assert_eq!(clipped.0, start);
350 assert_eq!(clipped.1, end);
351 }
352
353 #[test]
354 fn build_segments_with_transform() {
355 let viewport = Viewport::new(Range::new(0.0, 1.0), Range::new(0.0, 1.0));
356 let rect = ScreenRect::new(ScreenPoint::new(0.0, 0.0), ScreenPoint::new(10.0, 10.0));
357 let transform = Transform::new(viewport, rect).expect("valid transform");
358 let points = [Point::new(0.0, 0.0), Point::new(1.0, 1.0)];
359 let mut out = Vec::new();
360 build_line_segments(&points, &transform, rect, &mut out);
361 assert_eq!(out.len(), 1);
362 }
363}