Skip to main content

egui_charts/drawings/rendering/
traits.rs

1//! Drawing renderer trait and rendering utilities.
2//!
3//! Re-exports the [`DrawingRenderer`] trait for rendering drawing tools and provides the
4//! utility functions [`render_handles`], [`point_near_line`] and [`point_near_points`] for
5//! custom renderers.
6
7use crate::tokens::DESIGN_TOKENS;
8use egui::{Color32, Painter, Pos2};
9
10pub use crate::drawings::registry::DrawingRenderer;
11
12/// Helper function to render selection handles
13pub fn render_handles(painter: &Painter, points: &[Pos2], size: f32, selected: bool) {
14    if !selected || points.is_empty() {
15        return;
16    }
17
18    let handle_color = DESIGN_TOKENS.semantic.extended.accent; // Brand blue
19    let border_color = Color32::WHITE;
20    let radius = size / 2.0;
21
22    for &point in points {
23        // White border
24        painter.circle_filled(point, radius + 1.0, border_color);
25        // Blue fill
26        painter.circle_filled(point, radius, handle_color);
27    }
28}
29
30/// Helper function to check if a point is near a line segment
31pub fn point_near_line(point: Pos2, line_start: Pos2, line_end: Pos2, tolerance: f32) -> bool {
32    let line_vec = line_end - line_start;
33    let line_len_sq = line_vec.length_sq();
34
35    if line_len_sq < 1e-6 {
36        // Degenerate line (points are same)
37        return (point - line_start).length() <= tolerance;
38    }
39
40    // Project point onto line
41    let t = ((point - line_start).dot(line_vec) / line_len_sq).clamp(0.0, 1.0);
42    let projection = line_start + t * line_vec;
43    let distance = (point - projection).length();
44
45    distance <= tolerance
46}
47
48/// Helper function to check if a point is near any point in a list
49pub fn point_near_points(point: Pos2, points: &[Pos2], tolerance: f32) -> Option<usize> {
50    for (i, &p) in points.iter().enumerate() {
51        if (point - p).length() <= tolerance {
52            return Some(i);
53        }
54    }
55    None
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn test_point_near_line() {
64        let start = Pos2::new(0.0, 0.0);
65        let end = Pos2::new(100.0, 0.0);
66
67        // Point on line
68        assert!(point_near_line(Pos2::new(50.0, 0.0), start, end, 5.0));
69
70        // Point near line
71        assert!(point_near_line(Pos2::new(50.0, 3.0), start, end, 5.0));
72
73        // Point far from line
74        assert!(!point_near_line(Pos2::new(50.0, 10.0), start, end, 5.0));
75
76        // Point beyond line segment (near start)
77        assert!(point_near_line(Pos2::new(-3.0, 0.0), start, end, 5.0));
78
79        // Point beyond line segment (far from start)
80        assert!(!point_near_line(Pos2::new(-10.0, 0.0), start, end, 5.0));
81    }
82
83    #[test]
84    fn test_point_near_points() {
85        let points = vec![
86            Pos2::new(0.0, 0.0),
87            Pos2::new(100.0, 100.0),
88            Pos2::new(50.0, 50.0),
89        ];
90
91        // Near first point
92        assert_eq!(
93            point_near_points(Pos2::new(3.0, 0.0), &points, 5.0),
94            Some(0)
95        );
96
97        // Near third point
98        assert_eq!(
99            point_near_points(Pos2::new(52.0, 50.0), &points, 5.0),
100            Some(2)
101        );
102
103        // Not near any point
104        assert_eq!(
105            point_near_points(Pos2::new(200.0, 200.0), &points, 5.0),
106            None
107        );
108    }
109}