use egui::emath::TSTransform;
use egui::{Color32, Id, LayerId, Painter, Pos2, Rect, Shape, Stroke, StrokeKind, Ui, pos2};
#[derive(Default)]
pub(crate) struct Shapes(Vec<Shape>);
impl Shapes {
pub(crate) fn add(&mut self, shape: impl Into<Shape>) {
self.0.push(shape.into());
}
pub(crate) fn line_segment(&mut self, points: [Pos2; 2], stroke: impl Into<Stroke>) {
self.add(Shape::line_segment(points, stroke));
}
pub(crate) fn rect(
&mut self,
rect: Rect,
corner_radius: impl Into<egui::CornerRadius>,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
stroke_kind: StrokeKind,
) {
self.add(egui::epaint::RectShape::new(
rect,
corner_radius,
fill_color,
stroke,
stroke_kind,
));
}
pub(crate) fn rect_stroke(
&mut self,
rect: Rect,
corner_radius: impl Into<egui::CornerRadius>,
stroke: impl Into<Stroke>,
stroke_kind: StrokeKind,
) {
self.add(egui::epaint::RectShape::stroke(
rect,
corner_radius,
stroke,
stroke_kind,
));
}
pub(crate) fn circle(
&mut self,
center: Pos2,
radius: f32,
fill_color: impl Into<Color32>,
stroke: impl Into<Stroke>,
) {
self.add(egui::epaint::CircleShape {
center,
radius,
fill: fill_color.into(),
stroke: stroke.into(),
});
}
pub(crate) fn into_shape(self) -> Shape {
Shape::Vec(self.0)
}
}
pub(crate) fn canvas_painter(clip_rect: Rect, child_ui: &Ui, host_layer: LayerId) -> Painter {
let layer = LayerId::new(host_layer.order, Id::new(("editor_painter", host_layer)));
child_ui.ctx().set_sublayer(host_layer, layer);
let mut painter = child_ui.ctx().layer_painter(layer);
painter.set_clip_rect(clip_rect);
painter
}
fn draw_grid(
edge_painter: &mut Shapes,
rect: Rect,
tst: &TSTransform,
spacing: f32,
stroke: Stroke,
) {
let mut x = (rect.left() / spacing).floor() * spacing;
while x <= rect.right() {
edge_painter.line_segment(
[*tst * pos2(x, rect.top()), *tst * pos2(x, rect.bottom())],
stroke,
);
x += spacing;
}
let mut y = (rect.top() / spacing).floor() * spacing;
while y <= rect.bottom() {
edge_painter.line_segment(
[*tst * pos2(rect.left(), y), *tst * pos2(rect.right(), y)],
stroke,
);
y += spacing;
}
}
const MIN_GRID_PX: f32 = 6.0;
pub(crate) fn draw_adaptive_grid(
shapes: &mut Shapes,
rect: Rect,
transform: &TSTransform,
levels: &[(f32, Stroke)],
) {
for (spacing, stroke) in levels {
if spacing * transform.scaling >= MIN_GRID_PX {
draw_grid(shapes, rect, transform, *spacing, *stroke);
}
}
}
pub(crate) fn draw_edge(edge_painter: &mut Shapes, prev_terminal: &Pos2, next_terminal: &Pos2) {
edge_painter.line_segment([*prev_terminal, *next_terminal], (1.0, Color32::WHITE));
let direction = (*next_terminal - *prev_terminal).normalized();
let perp = egui::vec2(-direction.y, direction.x);
const ARROW_SIZE: f32 = 8.0;
let arrow_points = [
*next_terminal,
*next_terminal - direction * ARROW_SIZE + perp * (ARROW_SIZE / 2.0),
*next_terminal - direction * ARROW_SIZE - perp * (ARROW_SIZE / 2.0),
];
edge_painter.add(Shape::convex_polygon(
Vec::from(arrow_points),
Color32::WHITE,
Stroke::NONE,
));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_canvas_paints_under_a_layer_that_is_not_itself_a_sublayer() {
egui::__run_test_ui(|ui| {
let scene = egui::LayerId::new(ui.layer_id().order, ui.id().with("scene_area"));
ui.ctx().set_sublayer(ui.layer_id(), scene);
let host = ui.layer_id();
let painter = canvas_painter(ui.clip_rect(), ui, host);
let parent = ui
.ctx()
.memory(|memory| memory.areas().parent_layer(painter.layer_id()));
assert_eq!(parent, Some(host));
assert_eq!(
ui.ctx().memory(|memory| memory.areas().parent_layer(host)),
None,
"the host is a layer of its own, so the nesting stops at one level"
);
});
}
#[test]
fn an_edge_carries_its_arrowhead_at_the_end() {
let mut shapes = Shapes::default();
let (from, to) = (pos2(0.0, 0.0), pos2(100.0, 0.0));
draw_edge(&mut shapes, &from, &to);
let head = shapes
.0
.iter()
.find_map(|shape| match shape {
Shape::Path(path) => Some(path.points.clone()),
_ => None,
})
.expect("an arrowhead");
assert_eq!(head[0], to, "the tip is on the end");
assert!(
head[1..].iter().all(|point| point.x < to.x),
"the barbs trail behind it: {head:?}"
);
}
}