use crate::core::engine::opengl::{
GL_POINTS, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_TRIANGLES, GLfloat, Vec2,
};
use crate::core::{
Attribute, Color, Geometry, Mesh, Renderable, Renderer, Shader, generate_texture_from_image,
load_image,
};
use crate::graphics2d::shapes::{
Arc as ArcShape, Circle, Ellipse, Image, Line, MultiPoint, Polygon, Polyline, Rectangle,
RoundedRectangle, ShapeKind, Triangle,
};
use crate::graphics2d::svg::ToSvg;
use glam::{Mat4, Vec3};
use std::cell::OnceCell;
use std::f32::consts::PI;
use std::rc::Rc;
const MIN_STROKE_WIDTH: f32 = 1.5;
const SCALE_FACTOR: f32 = 1.0;
#[derive(Clone, Debug)]
pub struct ShapeStyle {
pub fill: Option<Color>,
pub stroke_color: Option<Color>,
pub stroke_width: Option<f32>,
}
impl Default for ShapeStyle {
fn default() -> Self {
Self {
fill: Some(Color::from_rgb(1.0, 1.0, 1.0)),
stroke_color: Some(Color::from_rgb(1.0, 1.0, 1.0)),
stroke_width: Some(5.0),
}
}
}
thread_local! {
static DEFAULT_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn default_shader() -> Rc<Shader> {
DEFAULT_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/shape.vert");
let frag_src = include_str!("../shaders/shape.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None)
.expect("Failed to compile default shader"),
)
})
.clone()
})
}
thread_local! {
static POINT_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn point_shader() -> Rc<Shader> {
POINT_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/shape.vert");
let frag_src = include_str!("../shaders/point.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None).expect("Failed to compile point shader"),
)
})
.clone()
})
}
thread_local! {
static IMAGE_SHADER: OnceCell<Rc<Shader>> = OnceCell::new();
}
fn image_shader() -> Rc<Shader> {
IMAGE_SHADER.with(|cell| {
cell.get_or_init(|| {
let vert_src = include_str!("../shaders/image.vert");
let frag_src = include_str!("../shaders/image.frag");
Rc::new(
Shader::compile(vert_src, frag_src, None).expect("Failed to compile image shader"),
)
})
.clone()
})
}
fn ortho_2d_with_zoom(width: f32, height: f32, zoom: f32) -> Mat4 {
let half_w = width * 0.5 / zoom;
let half_h = height * 0.5 / zoom;
let left = width * 0.5 - half_w;
let right = width * 0.5 + half_w;
let top = height * 0.5 - half_h;
let bottom = height * 0.5 + half_h;
Mat4::orthographic_rh_gl(left, right, bottom, top, 0.0, 1.0)
}
pub struct ShapeRenderable {
x: f32,
y: f32,
mesh: Mesh,
shape: ShapeKind,
}
impl Renderable for ShapeRenderable {
fn render(&mut self, renderer: &Renderer) {
let (window_width, window_height) = renderer.window_handle.size();
let transform = ortho_2d_with_zoom(window_width as f32, window_height as f32, renderer.zoom_level)
* Mat4::from_scale(Vec3::splat(SCALE_FACTOR));
self.mesh.set_transform(transform);
if self.mesh.geometry.instance_count() > 0 {
renderer.draw_mesh_instanced(&self.mesh);
} else {
self.mesh.set_screen_offset(self.x, self.y);
renderer.draw_mesh(&self.mesh);
}
}
}
impl ShapeRenderable {
fn new(x: f32, y: f32, mesh: Mesh, shape: ShapeKind) -> Self {
Self { x, y, mesh, shape }
}
pub fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
pub fn from_shape(x: f32, y: f32, shape: ShapeKind, style: ShapeStyle) -> Self {
match shape {
ShapeKind::Point => {
ShapeRenderable::point(x, y, style.fill.unwrap_or(Color::white()))
}
ShapeKind::MultiPoint(mp) => {
ShapeRenderable::multi_points(x, y, mp, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Line(line) => ShapeRenderable::line(
x,
y,
line,
style.stroke_color.unwrap_or_else(Color::white),
style.stroke_width.unwrap_or(1.0),
),
ShapeKind::Polyline(poly_line) => {
ShapeRenderable::polyline(
x,
y,
poly_line,
style.stroke_color.unwrap_or(Color::white()),
style.stroke_width.unwrap_or(1.0),
)
}
ShapeKind::Triangle(triangle) => {
ShapeRenderable::triangle(x, y, triangle, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Rectangle(rect) => {
ShapeRenderable::rectangle(x, y, rect, style.fill.unwrap_or(Color::white()))
}
ShapeKind::RoundedRectangle(rr) => {
ShapeRenderable::rounded_rectangle(x, y, rr, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Polygon(polygon) => {
ShapeRenderable::polygon(x, y, polygon, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Circle(circle) => {
ShapeRenderable::circle(x, y, circle, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Ellipse(ellipse) => {
ShapeRenderable::ellipse(x, y, ellipse, style.fill.unwrap_or(Color::white()))
}
ShapeKind::Arc(arc) => ShapeRenderable::arc(
x,
y,
arc,
style.stroke_color.unwrap_or(Color::white()),
style.stroke_width.unwrap_or(1.0),
),
ShapeKind::Image(_) => {
unimplemented!("ShapeRenderable::from_shape cannot create Image without path")
}
}
}
pub fn create_multiple_instances(&mut self, capacity: usize) {
self.mesh.geometry.enable_instancing_xy(capacity);
}
pub fn set_instance_positions(&mut self, positions: &[Vec2]) {
self.mesh.geometry.update_instance_xy(positions);
}
pub fn clear_instances(&mut self) {
self.mesh.geometry.clear_instancing();
}
fn point(x: GLfloat, y: GLfloat, color: Color) -> Self {
let geometry = ShapeRenderable::point_geometry();
let mesh = Mesh::with_color(point_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Point)
}
fn multi_points(x: GLfloat, y: GLfloat, multi_point: MultiPoint, color: Color) -> Self {
let abs_points: Vec<(f32, f32)> = multi_point
.points
.iter()
.map(|(px, py)| (x + px, y + py))
.collect();
let (x0, y0) = abs_points[0];
let rel_points: Vec<(GLfloat, GLfloat)> =
abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::point_list_geometry(&rel_points);
let mesh = Mesh::with_color(point_shader(), geometry, Some(color));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::MultiPoint(multi_point))
}
fn line(
x1: GLfloat,
y1: GLfloat,
shape: Line,
stroke: Color,
stroke_width: f32,
) -> Self {
let rel_x2 = shape.x2 - x1;
let rel_y2 = shape.y2 - y1;
let geometry = ShapeRenderable::line_geometry(0.0, 0.0, rel_x2, rel_y2, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x1, y1, mesh, ShapeKind::Line(shape))
}
fn polyline(
x: f32,
y: f32,
polyline: Polyline,
stroke: Color,
stroke_width: f32,
) -> Self {
assert!(polyline.points.len() >= 2, "Polyline requires at least two points");
let abs_points: Vec<(f32, f32)> =
polyline.points.iter().map(|(px, py)| (x + px, y + py)).collect();
let (x0, y0) = abs_points[0];
let rel_points: Vec<(f32, f32)> = abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polyline_geometry(&rel_points, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::Polyline(polyline))
}
fn polyline_from_points(
points: &[(f32, f32)],
shape: ShapeKind,
stroke: Color,
stroke_width: f32,
) -> Self {
assert!(points.len() >= 2, "Polyline requires at least two points");
let (x0, y0) = points[0];
let rel_points: Vec<(f32, f32)> = points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polyline_geometry(&rel_points, stroke_width);
let mesh = Mesh::with_color(default_shader(), geometry, Some(stroke));
ShapeRenderable::new(x0, y0, mesh, shape)
}
fn arc(x: f32, y: f32, arc: ArcShape, stroke: Color, stroke_width: f32) -> Self {
use std::f32::consts::TAU;
let segments = 64;
let mut sweep = arc.end_angle - arc.start_angle;
if sweep < 0.0 {
sweep += TAU;
}
let mut points = Vec::with_capacity(segments + 1);
for i in 0..=segments {
let t = i as f32 / segments as f32;
let theta = arc.start_angle + t * sweep;
let px = x + arc.radius * theta.cos();
let py = y - arc.radius * theta.sin();
points.push((px, py));
}
Self::polyline_from_points(&points, ShapeKind::Arc(arc), stroke, stroke_width)
}
fn triangle(x: f32, y: f32, triangle: Triangle, color: Color) -> Self {
let geometry = ShapeRenderable::triangle_geometry(&triangle.vertices);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Triangle(triangle))
}
fn rectangle(x: f32, y: f32, rect: Rectangle, color: Color) -> Self {
let geometry = ShapeRenderable::rectangle_geometry(rect.width, rect.height);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Rectangle(rect))
}
fn rounded_rectangle(x: f32, y: f32, rr: RoundedRectangle, color: Color) -> Self {
let geometry =
ShapeRenderable::rounded_rectangle_geometry(rr.width, rr.height, rr.radius, 8);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::RoundedRectangle(rr))
}
fn polygon(x: f32, y: f32, polygon: Polygon, color: Color) -> Self {
assert!(polygon.points.len() >= 3, "Polygon requires at least 3 points");
let abs_points: Vec<(f32, f32)> =
polygon.points.iter().map(|(px, py)| (x + px, y + py)).collect();
let (x0, y0) = abs_points[0]; let rel_points: Vec<(f32, f32)> = abs_points.iter().map(|(x, y)| (x - x0, y - y0)).collect();
let geometry = ShapeRenderable::polygon_geometry(&rel_points);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x0, y0, mesh, ShapeKind::Polygon(polygon))
}
fn circle(x: f32, y: f32, circle: Circle, color: Color) -> Self {
let geometry = ShapeRenderable::circle_geometry(circle.radius, 100);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Circle(circle))
}
fn ellipse(x: f32, y: f32, ellipse: Ellipse, color: Color) -> Self {
let geometry = ShapeRenderable::ellipse_geometry(ellipse.radius_x, ellipse.radius_y, 64);
let mesh = Mesh::with_color(default_shader(), geometry, Some(color));
ShapeRenderable::new(x, y, mesh, ShapeKind::Ellipse(ellipse))
}
pub fn image_with_size(x: f32, y: f32, path: &str, width: f32, height: f32) -> ShapeRenderable {
let image = load_image(path);
let texture_id = generate_texture_from_image(&image);
let geometry = ShapeRenderable::image_geometry(width, height);
let shader = image_shader(); let mesh = Mesh::with_texture(shader, geometry, Some(texture_id));
ShapeRenderable::new(x, y, mesh, ShapeKind::Image(Image::new(width, height)))
}
pub fn image(x: f32, y: f32, path: &str) -> Self {
let image = load_image(path);
Self::image_with_size(x, y, path, image.width as f32, image.height as f32)
}
fn point_geometry() -> Geometry {
let vertex = vec![0.0, 0.0];
let mut geometry = Geometry::new(GL_POINTS);
geometry.add_buffer(&vertex, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn point_list_geometry(points: &[(GLfloat, GLfloat)]) -> Geometry {
let mut vertices = Vec::with_capacity(points.len() * 2);
for &(x, y) in points {
vertices.push(x);
vertices.push(y);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_POINTS);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn line_geometry(
x1: GLfloat,
y1: GLfloat,
x2: GLfloat,
y2: GLfloat,
stroke_width: f32,
) -> Geometry {
let stroke_width = stroke_width.max(MIN_STROKE_WIDTH);
let dx = x2 - x1;
let dy = y2 - y1;
let length = (dx * dx + dy * dy).sqrt();
if length == 0.0 {
return Geometry::new(GL_TRIANGLES);
}
let nx = -dy / length;
let ny = dx / length;
let half_thickness = stroke_width / 2.0;
let ox = nx * half_thickness;
let oy = ny * half_thickness;
let v0 = [x1 - ox, y1 - oy];
let v1 = [x2 - ox, y2 - oy];
let v2 = [x2 + ox, y2 + oy];
let v3 = [x1 + ox, y1 + oy];
let vertices: Vec<GLfloat> = vec![
v0[0], v0[1], v1[0], v1[1], v2[0], v2[1], v2[0], v2[1], v3[0], v3[1], v0[0], v0[1],
];
let position_values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, position_values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
position_values_per_vertex as usize,
0,
));
geometry
}
fn polyline_geometry(points: &[(GLfloat, GLfloat)], stroke_width: f32) -> Geometry {
const MITER_LIMIT: f32 = 4.0;
if points.len() < 2 {
return Geometry::new(GL_TRIANGLES);
}
let half_thickness = stroke_width.max(1.0) / 2.0;
let miter_limit_squared = (stroke_width * MITER_LIMIT).powi(2) / 4.0;
let mut vertices: Vec<GLfloat> = Vec::new();
let mut a = points[0];
let mut b = points[1];
let mut idx = 1;
while idx < points.len() && (b.0 - a.0).hypot(b.1 - a.1) == 0.0 {
idx += 1;
if idx < points.len() {
b = points[idx];
}
}
if (b.0 - a.0).hypot(b.1 - a.1) == 0.0 {
return Geometry::new(GL_TRIANGLES);
}
for i in idx + 1..=points.len() {
let c = if i < points.len() { points[i] } else { a };
let ab = (b.0 - a.0, b.1 - a.1);
let len_ab = (ab.0 * ab.0 + ab.1 * ab.1).sqrt();
let normal_ab = (
-ab.1 / len_ab * half_thickness,
ab.0 / len_ab * half_thickness,
);
let a1 = (a.0 + normal_ab.0, a.1 + normal_ab.1);
let a2 = (a.0 - normal_ab.0, a.1 - normal_ab.1);
let b1 = (b.0 + normal_ab.0, b.1 + normal_ab.1);
let b2 = (b.0 - normal_ab.0, b.1 - normal_ab.1);
vertices.extend_from_slice(&[
a1.0, a1.1, a2.0, a2.1, b1.0, b1.1, a2.0, a2.1, b1.0, b1.1, b2.0, b2.1,
]);
let bc = (c.0 - b.0, c.1 - b.1);
let len_bc = (bc.0 * bc.0 + bc.1 * bc.1).sqrt();
if len_bc > 0.0 {
let normal_bc = (
-bc.1 / len_bc * half_thickness,
bc.0 / len_bc * half_thickness,
);
let b3 = (b.0 + normal_bc.0, b.1 + normal_bc.1);
let b4 = (b.0 - normal_bc.0, b.1 - normal_bc.1);
let z = ab.0 * bc.1 - ab.1 * bc.0;
if z < 0.0 {
vertices.extend_from_slice(&[b.0, b.1, b1.0, b1.1, b3.0, b3.1]);
} else if z > 0.0 {
vertices.extend_from_slice(&[b.0, b.1, b2.0, b2.1, b4.0, b4.1]);
}
if z != 0.0 {
let (a_j, b_j, norm_j) = if z < 0.0 { (a1, b3, ab) } else { (a2, b4, ab) };
let denom = z;
let alpha = (bc.1 * (b_j.0 - a_j.0) + bc.0 * (a_j.1 - b_j.1)) / denom;
let mx = a_j.0 + alpha * norm_j.0;
let my = a_j.1 + alpha * norm_j.1;
let dist2 = (mx - b.0).powi(2) + (my - b.1).powi(2);
if dist2 <= miter_limit_squared {
if z < 0.0 {
vertices.extend_from_slice(&[mx, my, b1.0, b1.1, b3.0, b3.1]);
} else {
vertices.extend_from_slice(&[mx, my, b2.0, b2.1, b4.0, b4.1]);
}
}
}
}
a = b;
b = c;
}
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn triangle_geometry(vertices: &[(f32, f32); 3]) -> Geometry {
let mut geometry = Geometry::new(GL_TRIANGLES);
let flattened: Vec<f32> = vertices.iter().flat_map(|(x, y)| [*x, *y]).collect();
geometry.add_buffer(&flattened, 2);
geometry.add_vertex_attribute(Attribute::new(0, 2, 2, 0));
geometry
}
fn rectangle_geometry(width: GLfloat, height: GLfloat) -> Geometry {
let vertices: Vec<GLfloat> = vec![
0.0, 0.0, width, 0.0, 0.0, height, width, height,
];
let position_values_per_vertex = 2;
let values_per_vertex = position_values_per_vertex;
let mut geometry = Geometry::new(GL_TRIANGLE_STRIP);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn circle_geometry(radius: GLfloat, segments: usize) -> Geometry {
let mut vertices: Vec<GLfloat> = Vec::with_capacity((segments + 2) * 5);
vertices.extend_from_slice(&[0.0, 0.0]);
for i in 0..=segments {
let theta = (i as f32 / segments as f32) * std::f32::consts::TAU; let x = radius * theta.cos();
let y = radius * theta.sin();
vertices.extend_from_slice(&[x, y]);
}
let position_values_per_vertex = 2;
let values_per_vertex = position_values_per_vertex;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0,
position_values_per_vertex,
values_per_vertex as usize,
0,
));
geometry
}
fn ellipse_geometry(rx: f32, ry: f32, segments: usize) -> Geometry {
use std::f32::consts::PI;
let mut vertices: Vec<GLfloat> = Vec::with_capacity((segments + 2) * 2);
vertices.extend_from_slice(&[0.0, 0.0]);
for i in 0..=segments {
let angle = 2.0 * PI * (i as f32) / (segments as f32);
let x = rx * angle.cos();
let y = ry * angle.sin();
vertices.extend_from_slice(&[x, y]);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2,
values_per_vertex as usize,
0,
));
geometry
}
pub fn rounded_rectangle_geometry(
width: f32,
height: f32,
radius: f32,
segments: usize,
) -> Geometry {
assert!(radius * 2.0 <= width && radius * 2.0 <= height);
let mut vertices: Vec<GLfloat> = Vec::new();
let center_x = width / 2.0;
let center_y = height / 2.0;
vertices.push(center_x);
vertices.push(center_y);
let corners = [
(radius, radius, PI, 1.5 * PI), (width - radius, radius, 1.5 * PI, 2.0 * PI), (width - radius, height - radius, 0.0, 0.5 * PI), (radius, height - radius, 0.5 * PI, PI), ];
let mut first_arc_x = 0.0;
let mut first_arc_y = 0.0;
let mut is_first = true;
for &(cx, cy, start_angle, end_angle) in &corners {
for i in 0..=segments {
let theta =
start_angle + (end_angle - start_angle) * (i as f32) / (segments as f32);
let x = cx + radius * theta.cos();
let y = cy + radius * theta.sin();
if is_first {
first_arc_x = x;
first_arc_y = y;
is_first = false;
}
vertices.push(x);
vertices.push(y);
}
}
vertices.push(first_arc_x);
vertices.push(first_arc_y);
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2, values_per_vertex as usize,
0,
));
geometry
}
fn polygon_geometry(points: &[(GLfloat, GLfloat)]) -> Geometry {
assert!(points.len() >= 3, "Polygon requires at least 3 points");
let mut vertices = Vec::with_capacity(points.len() * 2);
for &(x, y) in points {
vertices.extend_from_slice(&[x, y]);
}
let values_per_vertex = 2;
let mut geometry = Geometry::new(GL_TRIANGLE_FAN); geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(0, 2, values_per_vertex as usize, 0));
geometry
}
pub fn image_geometry(width: f32, height: f32) -> Geometry {
let vertices: Vec<f32> = vec![
0.0, 0.0, 0.0, 0.0, width, 0.0, 1.0, 0.0, width, height, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, width, height, 1.0, 1.0, 0.0, height, 0.0, 1.0, ];
let values_per_vertex = 4;
let mut geometry = Geometry::new(GL_TRIANGLES);
geometry.add_buffer(&vertices, values_per_vertex);
geometry.add_vertex_attribute(Attribute::new(
0, 2, values_per_vertex as usize,
0,
));
geometry.add_vertex_attribute(Attribute::new(
1, 2, values_per_vertex as usize,
2, ));
geometry
}
fn svg_color(&self) -> String {
self.mesh
.color
.as_ref()
.map(|c| c.to_hex())
.unwrap_or_else(|| "#000000".to_string())
}
}
impl ToSvg for ShapeRenderable {
fn to_svg(&self) -> String {
match &self.shape {
ShapeKind::Line(line) => {
format!(
r#"<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="{color}" stroke-width="1"/>"#,
x1 = self.x,
y1 = self.y,
x2 = line.x2,
y2 = line.y2,
color = self.svg_color(),
)
}
ShapeKind::Rectangle(rect) => {
format!(
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" fill="{color}"/>"#,
x = self.x,
y = self.y,
w = rect.width,
h = rect.height,
color = self.svg_color(),
)
}
ShapeKind::RoundedRectangle(rect) => {
format!(
r#"<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{r}" ry="{r}" fill="{color}"/>"#,
x = self.x,
y = self.y,
w = rect.width,
h = rect.height,
r = rect.radius,
color = self.svg_color(),
)
}
ShapeKind::Polygon(polygon) => {
let path = polygon
.points
.iter()
.map(|(px, py)| format!("{},{}", px + self.x, py + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polygon points="{path}" fill="{color}" stroke="{color}" stroke-width="1"/>"#,
path = path,
color = self.svg_color(),
)
}
ShapeKind::Circle(circle) => {
format!(
r#"<circle cx="{cx}" cy="{cy}" r="{r}" fill="{color}"/>"#,
cx = self.x + circle.radius,
cy = self.y + circle.radius,
r = circle.radius,
color = self.svg_color(),
)
}
ShapeKind::Ellipse(ellipse) => {
format!(
r#"<ellipse cx="{cx}" cy="{cy}" rx="{rx}" ry="{ry}" fill="{color}"/>"#,
cx = self.x + ellipse.radius_x,
cy = self.y + ellipse.radius_y,
rx = ellipse.radius_x,
ry = ellipse.radius_y,
color = self.svg_color(),
)
}
ShapeKind::Polyline(polyline) => {
let path = polyline
.points
.iter()
.map(|(px, py)| format!("{},{}", px + self.x, py + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polyline points="{path}" fill="none" stroke="{color}" stroke-width="1"/>"#,
path = path,
color = self.svg_color(),
)
}
ShapeKind::MultiPoint(multi_point) => {
let mut out = String::new();
for (px, py) in &multi_point.points {
let cx = px + self.x;
let cy = py + self.y;
out.push_str(&format!(
r#"<circle cx="{cx}" cy="{cy}" r="2" fill="{color}"/>"#,
cx = cx,
cy = cy,
color = self.svg_color(),
));
}
out
}
ShapeKind::Point => {
format!(
r#"<circle cx="{cx}" cy="{cy}" r="2" fill="{color}"/>"#,
cx = self.x,
cy = self.y,
color = self.svg_color(),
)
}
ShapeKind::Image(_) => String::new(),
ShapeKind::Triangle(tri) => {
let points: String = tri
.vertices
.iter()
.map(|(vx, vy)| format!("{:.2},{:.2}", vx + self.x, vy + self.y))
.collect::<Vec<_>>()
.join(" ");
format!(
r#"<polygon points="{points}" fill="{color}"/>"#,
points = points,
color = self.svg_color(),
)
}
ShapeKind::Arc(_) => {
unimplemented!("Arc SVG export is not yet implemented")
}
}
}
}