use crate::tokens::DESIGN_TOKENS;
use egui::{Color32, Painter, Pos2, Rect, Stroke};
pub trait DrawingRenderer {
fn render(&self, painter: &Painter, chart_rect: Rect, is_selected: bool, is_hovered: bool);
fn bounding_box(&self) -> Option<Rect>;
fn hit_test(&self, point: Pos2, tolerance: f32) -> bool;
fn anchor_points(&self) -> Vec<Pos2>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HandleHit {
None,
Point(usize),
Body,
}
#[derive(Clone)]
pub struct RenderContext<'a> {
pub painter: &'a Painter,
pub chart_rect: Rect,
pub stroke: Stroke,
pub fill_color: Color32,
pub show_handles: bool,
pub handle_size: f32,
}
impl<'a> RenderContext<'a> {
pub fn new(painter: &'a Painter, chart_rect: Rect) -> Self {
Self {
painter,
chart_rect,
stroke: Stroke::new(
DESIGN_TOKENS.stroke.medium,
DESIGN_TOKENS.semantic.extended.info,
),
fill_color: {
let info = DESIGN_TOKENS.semantic.extended.info;
Color32::from_rgba_unmultiplied(info.r(), info.g(), info.b(), 50)
},
show_handles: false,
handle_size: 8.0,
}
}
pub fn with_stroke(mut self, stroke: Stroke) -> Self {
self.stroke = stroke;
self
}
pub fn with_fill(mut self, color: Color32) -> Self {
self.fill_color = color;
self
}
pub fn with_handles(mut self, show: bool, size: f32) -> Self {
self.show_handles = show;
self.handle_size = size;
self
}
}
pub fn render_handles(painter: &Painter, points: &[Pos2], size: f32, selected: bool) {
if !selected || points.is_empty() {
return;
}
let handle_color = DESIGN_TOKENS.semantic.extended.accent; let border_color = Color32::WHITE;
let radius = size / 2.0;
for &point in points {
painter.circle_filled(point, radius + 1.0, border_color);
painter.circle_filled(point, radius, handle_color);
}
}
pub fn point_near_line(point: Pos2, line_start: Pos2, line_end: Pos2, tolerance: f32) -> bool {
let line_vec = line_end - line_start;
let line_len_sq = line_vec.length_sq();
if line_len_sq < 1e-6 {
return (point - line_start).length() <= tolerance;
}
let t = ((point - line_start).dot(line_vec) / line_len_sq).clamp(0.0, 1.0);
let projection = line_start + t * line_vec;
let distance = (point - projection).length();
distance <= tolerance
}
pub fn point_near_points(point: Pos2, points: &[Pos2], tolerance: f32) -> Option<usize> {
for (i, &p) in points.iter().enumerate() {
if (point - p).length() <= tolerance {
return Some(i);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_near_line() {
let start = Pos2::new(0.0, 0.0);
let end = Pos2::new(100.0, 0.0);
assert!(point_near_line(Pos2::new(50.0, 0.0), start, end, 5.0));
assert!(point_near_line(Pos2::new(50.0, 3.0), start, end, 5.0));
assert!(!point_near_line(Pos2::new(50.0, 10.0), start, end, 5.0));
assert!(point_near_line(Pos2::new(-3.0, 0.0), start, end, 5.0));
assert!(!point_near_line(Pos2::new(-10.0, 0.0), start, end, 5.0));
}
#[test]
fn test_point_near_points() {
let points = vec![
Pos2::new(0.0, 0.0),
Pos2::new(100.0, 100.0),
Pos2::new(50.0, 50.0),
];
assert_eq!(
point_near_points(Pos2::new(3.0, 0.0), &points, 5.0),
Some(0)
);
assert_eq!(
point_near_points(Pos2::new(52.0, 50.0), &points, 5.0),
Some(2)
);
assert_eq!(
point_near_points(Pos2::new(200.0, 200.0), &points, 5.0),
None
);
}
}