use bevy::prelude::*;
use lyon::tessellation::StrokeOptions;
use crate::draw::primitive::Line;
use crate::draw::primitive::Primitive;
use crate::draw::primitive::path;
use crate::draw::properties::spatial::{orientation, position};
use crate::draw::properties::{SetColor, SetOrientation, SetPosition, SetStroke};
use crate::draw::{self, Drawing};
#[derive(Clone, Debug)]
pub struct Arrow {
line: Line,
head_length: Option<f32>,
head_width: Option<f32>,
}
pub type DrawingArrow<'a> = Drawing<'a, Arrow>;
impl Arrow {
pub fn weight(self, weight: f32) -> Self {
self.map_line(|l| l.weight(weight))
}
pub fn tolerance(self, tolerance: f32) -> Self {
self.map_line(|l| l.tolerance(tolerance))
}
pub fn start(self, start: Vec2) -> Self {
self.map_line(|l| l.start(start))
}
pub fn end(self, end: Vec2) -> Self {
self.map_line(|l| l.end(end))
}
pub fn points(self, start: Vec2, end: Vec2) -> Self {
self.map_line(|l| l.points(start, end))
}
pub fn head_length(mut self, length: f32) -> Self {
self.head_length = Some(length);
self
}
pub fn head_width(mut self, width: f32) -> Self {
self.head_width = Some(width);
self
}
fn map_line<F>(self, map: F) -> Self
where
F: FnOnce(Line) -> Line,
{
let Arrow {
line,
head_length,
head_width,
} = self;
let line = map(line);
Arrow {
line,
head_length,
head_width,
}
}
}
impl<'a> DrawingArrow<'a> {
pub fn weight(self, weight: f32) -> Self {
self.stroke_weight(weight)
}
pub fn tolerance(self, tolerance: f32) -> Self {
self.stroke_tolerance(tolerance)
}
pub fn start(self, start: Vec2) -> Self {
update_arrow(&self.draw, self.index, |arrow| {
arrow.line.start = Some(start)
});
self
}
pub fn end(self, end: Vec2) -> Self {
update_arrow(&self.draw, self.index, |arrow| arrow.line.end = Some(end));
self
}
pub fn points(self, start: Vec2, end: Vec2) -> Self {
self.start(start).end(end)
}
pub fn head_length(self, length: f32) -> Self {
update_arrow(&self.draw, self.index, |arrow| {
arrow.head_length = Some(length)
});
self
}
pub fn head_width(self, width: f32) -> Self {
update_arrow(&self.draw, self.index, |arrow| {
arrow.head_width = Some(width)
});
self
}
}
impl SetStroke for Arrow {
fn stroke_options_mut(&mut self) -> &mut StrokeOptions {
SetStroke::stroke_options_mut(&mut self.line)
}
}
impl SetOrientation for Arrow {
fn properties(&mut self) -> &mut orientation::Properties {
SetOrientation::properties(&mut self.line)
}
}
impl SetPosition for Arrow {
fn properties(&mut self) -> &mut position::Properties {
SetPosition::properties(&mut self.line)
}
}
impl SetColor for Arrow {
fn color_mut(&mut self) -> &mut Option<Color> {
SetColor::color_mut(&mut self.line)
}
}
impl From<Arrow> for Primitive {
fn from(prim: Arrow) -> Self {
Primitive::Arrow(prim)
}
}
impl draw::render::RenderPrimitive for Arrow {
fn render_primitive(self, mut ctxt: draw::render::RenderContext, mesh: &mut Mesh) {
let Arrow {
line,
head_length,
head_width,
} = self;
let start = line.start.unwrap_or(Vec2::new(0.0, 0.0));
let end = line.end.unwrap_or(Vec2::new(0.0, 0.0));
if start == end {
return;
}
let line_w_2 = line.path.opts.line_width * 2.0;
let line_w_4 = line_w_2 * 2.0;
let head_width = head_width.unwrap_or(line_w_2);
let head_length = head_length.unwrap_or(line_w_4);
let line_dir = end - start;
let line_dir_len = line_dir.length();
let tri_len = head_length.min(line_dir_len);
let tri_dir_norm = line_dir.normalize() * tri_len;
let tri_start = end - tri_dir_norm;
let tri_end = end;
let line_start = start;
let line_end = tri_start;
let tri_a = tri_end;
let tri_w_dir = Vec2::new(-tri_dir_norm.y, tri_dir_norm.x).normalize() * head_width;
let tri_b = tri_start + tri_w_dir;
let tri_c = tri_start - tri_w_dir;
let draw_line = line_dir_len > tri_len;
let global_transform = *ctxt.transform;
let local_transform = line.path.position.transform() * line.path.orientation.transform();
let transform = global_transform * local_transform;
let tri_points = [tri_a, tri_b, tri_c];
let tri_tex_coords = [
Vec2::new(0.5, 1.0), Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0), ];
let tri_points = tri_points
.iter()
.cloned()
.zip(tri_tex_coords.iter().copied());
let close_tri = true;
path::render_path_points_themed(
tri_points,
close_tri,
line.path.color,
transform,
path::Options::Fill(Default::default()),
&ctxt.theme,
&draw::theme::Primitive::Arrow,
&mut ctxt.fill_tessellator,
&mut ctxt.stroke_tessellator,
mesh,
);
if draw_line {
let line_points = [line_start, line_end];
let line_tex_coords = [Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0)];
let line_points = line_points
.iter()
.cloned()
.zip(line_tex_coords.iter().copied());
let close_line = false;
path::render_path_points_themed(
line_points,
close_line,
line.path.color,
transform,
path::Options::Stroke(line.path.opts),
&ctxt.theme,
&draw::theme::Primitive::Arrow,
&mut ctxt.fill_tessellator,
&mut ctxt.stroke_tessellator,
mesh,
);
}
}
}
impl Default for Arrow {
fn default() -> Self {
let line = Default::default();
let head_length = Default::default();
let head_width = Default::default();
Arrow {
line,
head_length,
head_width,
}
}
}
fn update_arrow(draw: &crate::draw::Draw, index: usize, f: impl FnOnce(&mut Arrow)) {
crate::draw::drawing::with_primitive(draw, index, |prim| match prim {
Primitive::Arrow(arrow) => f(arrow),
_ => bevy::log::warn_once!("expected an `Arrow` primitive"),
})
}