use crate::color::conv::IntoLinSrgba;
use crate::color::LinSrgba;
use crate::draw::mesh::vertex::{Color, TexCoords};
use crate::draw::primitive::Primitive;
use crate::draw::properties::spatial::{orientation, position};
use crate::draw::properties::{
ColorScalar, SetColor, SetFill, SetOrientation, SetPosition, SetStroke,
};
use crate::draw::{self, Drawing, DrawingContext};
use crate::geom::{self, Point2};
use crate::math::{BaseFloat, Zero};
use crate::wgpu;
use lyon::path::PathEvent;
use lyon::tessellation::{FillOptions, FillTessellator, StrokeOptions, StrokeTessellator};
pub trait TessellationOptions {
type Tessellator;
fn into_options(self) -> Options;
}
#[derive(Clone, Debug)]
pub(crate) enum PathEventSource {
Buffered(std::ops::Range<usize>),
ColoredPoints {
range: std::ops::Range<usize>,
close: bool,
},
TexturedPoints {
range: std::ops::Range<usize>,
close: bool,
},
}
pub(crate) enum PathEventSourceIter<'a> {
Events(&'a mut dyn Iterator<Item = lyon::path::PathEvent>),
ColoredPoints {
points: &'a mut dyn Iterator<Item = (Point2, Color)>,
close: bool,
},
TexturedPoints {
points: &'a mut dyn Iterator<Item = (Point2, TexCoords)>,
close: bool,
},
}
#[derive(Clone, Debug)]
pub struct PathInit<S = geom::scalar::Default>(std::marker::PhantomData<S>);
#[derive(Clone, Debug)]
pub struct PathOptions<T, S = geom::scalar::Default> {
pub(crate) opts: T,
pub(crate) color: Option<LinSrgba>,
pub(crate) position: position::Properties<S>,
pub(crate) orientation: orientation::Properties<S>,
}
pub struct Tessellators<'a> {
pub fill: &'a mut FillTessellator,
pub stroke: &'a mut StrokeTessellator,
}
pub type PathFill<S = geom::scalar::Default> = PathOptions<FillOptions, S>;
pub type PathStroke<S = geom::scalar::Default> = PathOptions<StrokeOptions, S>;
#[derive(Clone, Debug)]
pub struct Path<S = geom::scalar::Default> {
color: Option<LinSrgba>,
position: position::Properties<S>,
orientation: orientation::Properties<S>,
path_event_src: PathEventSource,
options: Options,
vertex_mode: draw::renderer::VertexMode,
texture_view: Option<wgpu::TextureView>,
}
pub type DrawingPathInit<'a, S = geom::scalar::Default> = Drawing<'a, PathInit<S>, S>;
pub type DrawingPathOptions<'a, T, S = geom::scalar::Default> = Drawing<'a, PathOptions<T, S>, S>;
pub type DrawingPathStroke<'a, S = geom::scalar::Default> = Drawing<'a, PathStroke<S>, S>;
pub type DrawingPathFill<'a, S = geom::scalar::Default> = Drawing<'a, PathFill<S>, S>;
pub type DrawingPath<'a, S = geom::scalar::Default> = Drawing<'a, Path<S>, S>;
#[derive(Clone, Debug)]
pub enum Options {
Fill(FillOptions),
Stroke(StrokeOptions),
}
impl<S> PathInit<S> {
pub fn fill(self) -> PathFill<S>
where
S: Zero,
{
let opts = FillOptions::default();
PathFill::new(opts)
}
pub fn stroke(self) -> PathStroke<S>
where
S: Zero,
{
let opts = Default::default();
PathStroke::new(opts)
}
}
impl<T, S> PathOptions<T, S>
where
S: Zero,
{
pub fn new(opts: T) -> Self {
let orientation = Default::default();
let position = Default::default();
let color = Default::default();
PathOptions {
opts,
orientation,
position,
color,
}
}
}
impl<S> PathFill<S> {
pub fn tolerance(self, tolerance: f32) -> Self {
self.fill_tolerance(tolerance)
}
pub fn rule(self, rule: lyon::tessellation::FillRule) -> Self {
self.fill_rule(rule)
}
}
impl<S> PathStroke<S> {
pub fn weight(self, weight: f32) -> Self {
self.stroke_weight(weight)
}
pub fn tolerance(self, tolerance: f32) -> Self {
self.stroke_tolerance(tolerance)
}
}
impl<T, S> PathOptions<T, S>
where
T: TessellationOptions,
{
pub(crate) fn events<I>(self, ctxt: DrawingContext<S>, events: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = PathEvent>,
{
let DrawingContext {
path_event_buffer, ..
} = ctxt;
let start = path_event_buffer.len();
path_event_buffer.extend(events);
let end = path_event_buffer.len();
Path::new(
self.position,
self.orientation,
self.color,
PathEventSource::Buffered(start..end),
self.opts.into_options(),
draw::renderer::VertexMode::Color,
None,
)
}
pub fn points<I>(self, ctxt: DrawingContext<S>, points: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator,
I::Item: Into<Point2<S>>,
{
self.points_inner(ctxt, false, points)
}
pub fn points_closed<I>(self, ctxt: DrawingContext<S>, points: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator,
I::Item: Into<Point2<S>>,
{
self.points_inner(ctxt, true, points)
}
pub fn points_colored<I, P, C>(self, ctxt: DrawingContext<S>, points: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, C)>,
P: Into<Point2<S>>,
C: IntoLinSrgba<ColorScalar>,
{
self.points_colored_inner(ctxt, false, points)
}
pub fn points_colored_closed<I, P, C>(self, ctxt: DrawingContext<S>, points: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, C)>,
P: Into<Point2<S>>,
C: IntoLinSrgba<ColorScalar>,
{
self.points_colored_inner(ctxt, true, points)
}
pub fn points_textured<I, P, TC>(
self,
ctxt: DrawingContext<S>,
texture_view: &dyn wgpu::ToTextureView,
points: I,
) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, TC)>,
P: Into<Point2<S>>,
TC: Into<TexCoords<S>>,
{
self.points_textured_inner(ctxt, texture_view.to_texture_view(), false, points)
}
pub fn points_textured_closed<I, P, TC>(
self,
ctxt: DrawingContext<S>,
texture_view: &dyn wgpu::ToTextureView,
points: I,
) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, TC)>,
P: Into<Point2<S>>,
TC: Into<TexCoords<S>>,
{
self.points_textured_inner(ctxt, texture_view.to_texture_view(), true, points)
}
fn points_inner<I>(self, ctxt: DrawingContext<S>, close: bool, points: I) -> Path<S>
where
S: BaseFloat,
I: IntoIterator,
I::Item: Into<Point2<S>>,
{
let iter = points.into_iter().map(Into::into).map(|p| {
let p: geom::Point2 = p.cast().expect("failed to cast point");
lyon::math::point(p.x, p.y)
});
let events = lyon::path::iterator::FromPolyline::new(close, iter);
self.events(ctxt, events)
}
fn points_colored_inner<I, P, C>(
self,
ctxt: DrawingContext<S>,
close: bool,
points: I,
) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, C)>,
P: Into<Point2<S>>,
C: IntoLinSrgba<ColorScalar>,
{
let DrawingContext {
path_points_colored_buffer,
..
} = ctxt;
let start = path_points_colored_buffer.len();
let points = points
.into_iter()
.map(|(p, c)| (p.into(), c.into_lin_srgba()));
path_points_colored_buffer.extend(points);
let end = path_points_colored_buffer.len();
let path_event_src = PathEventSource::ColoredPoints {
range: start..end,
close,
};
Path::new(
self.position,
self.orientation,
self.color,
path_event_src,
self.opts.into_options(),
draw::renderer::VertexMode::Color,
None,
)
}
fn points_textured_inner<I, P, TC>(
self,
ctxt: DrawingContext<S>,
texture_view: wgpu::TextureView,
close: bool,
points: I,
) -> Path<S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, TC)>,
P: Into<Point2<S>>,
TC: Into<TexCoords<S>>,
{
let DrawingContext {
path_points_textured_buffer,
..
} = ctxt;
let start = path_points_textured_buffer.len();
let points = points.into_iter().map(|(p, tc)| (p.into(), tc.into()));
path_points_textured_buffer.extend(points);
let end = path_points_textured_buffer.len();
let path_event_src = PathEventSource::TexturedPoints {
range: start..end,
close,
};
Path::new(
self.position,
self.orientation,
self.color,
path_event_src,
self.opts.into_options(),
draw::renderer::VertexMode::Texture,
Some(texture_view),
)
}
}
pub(crate) fn render_path_events<I>(
events: I,
color: Option<LinSrgba>,
transform: cgmath::Matrix4<f32>,
options: Options,
theme: &draw::Theme,
theme_prim: &draw::theme::Primitive,
fill_tessellator: &mut lyon::tessellation::FillTessellator,
stroke_tessellator: &mut lyon::tessellation::StrokeTessellator,
mesh: &mut draw::Mesh,
) where
I: IntoIterator<Item = lyon::path::PathEvent>,
{
let res = match options {
Options::Fill(options) => {
let color = color.unwrap_or_else(|| theme.fill_lin_srgba(theme_prim));
let mut mesh_builder = draw::mesh::MeshBuilder::single_color(mesh, transform, color);
fill_tessellator.tessellate(events, &options, &mut mesh_builder)
}
Options::Stroke(options) => {
let color = color.unwrap_or_else(|| theme.stroke_lin_srgba(theme_prim));
let mut mesh_builder = draw::mesh::MeshBuilder::single_color(mesh, transform, color);
stroke_tessellator.tessellate(events, &options, &mut mesh_builder)
}
};
if let Err(err) = res {
eprintln!("failed to tessellate path: {:?}", err);
}
}
pub(crate) fn render_path_points_colored<I>(
points_colored: I,
close: bool,
transform: cgmath::Matrix4<f32>,
options: Options,
fill_tessellator: &mut lyon::tessellation::FillTessellator,
stroke_tessellator: &mut lyon::tessellation::StrokeTessellator,
mesh: &mut draw::Mesh,
) where
I: IntoIterator<Item = (Point2, Color)>,
{
let path = match points_colored_to_lyon_path(points_colored, close) {
None => return,
Some(p) => p,
};
let mut mesh_builder = draw::mesh::MeshBuilder::color_per_point(mesh, transform);
let res = match options {
Options::Fill(options) => fill_tessellator.tessellate_with_ids(
path.id_iter(),
&path,
Some(&path),
&options,
&mut mesh_builder,
),
Options::Stroke(options) => stroke_tessellator.tessellate_with_ids(
path.id_iter(),
&path,
Some(&path),
&options,
&mut mesh_builder,
),
};
if let Err(err) = res {
eprintln!("failed to tessellate path: {:?}", err);
}
}
pub(crate) fn render_path_points_textured<I>(
points_textured: I,
close: bool,
transform: cgmath::Matrix4<f32>,
options: Options,
fill_tessellator: &mut lyon::tessellation::FillTessellator,
stroke_tessellator: &mut lyon::tessellation::StrokeTessellator,
mesh: &mut draw::Mesh,
) where
I: IntoIterator<Item = (Point2, TexCoords)>,
{
let path = match points_textured_to_lyon_path(points_textured, close) {
None => return,
Some(p) => p,
};
let mut mesh_builder = draw::mesh::MeshBuilder::tex_coords_per_point(mesh, transform);
let res = match options {
Options::Fill(options) => fill_tessellator.tessellate_with_ids(
path.id_iter(),
&path,
Some(&path),
&options,
&mut mesh_builder,
),
Options::Stroke(options) => stroke_tessellator.tessellate_with_ids(
path.id_iter(),
&path,
Some(&path),
&options,
&mut mesh_builder,
),
};
if let Err(err) = res {
eprintln!("failed to tessellate path: {:?}", err);
}
}
pub(crate) fn render_path_source(
path_src: PathEventSourceIter,
color: Option<LinSrgba>,
transform: cgmath::Matrix4<f32>,
options: Options,
theme: &draw::Theme,
theme_prim: &draw::theme::Primitive,
fill_tessellator: &mut lyon::tessellation::FillTessellator,
stroke_tessellator: &mut lyon::tessellation::StrokeTessellator,
mesh: &mut draw::Mesh,
) {
match path_src {
PathEventSourceIter::Events(events) => render_path_events(
events,
color,
transform,
options,
theme,
theme_prim,
fill_tessellator,
stroke_tessellator,
mesh,
),
PathEventSourceIter::ColoredPoints { points, close } => render_path_points_colored(
points,
close,
transform,
options,
fill_tessellator,
stroke_tessellator,
mesh,
),
PathEventSourceIter::TexturedPoints { points, close } => render_path_points_textured(
points,
close,
transform,
options,
fill_tessellator,
stroke_tessellator,
mesh,
),
}
}
impl draw::renderer::RenderPrimitive for Path<f32> {
fn render_primitive(
self,
mut ctxt: draw::renderer::RenderContext,
mesh: &mut draw::Mesh,
) -> draw::renderer::PrimitiveRender {
let Path {
color,
position,
orientation,
path_event_src,
options,
vertex_mode,
texture_view,
} = self;
let global_transform = ctxt.transform;
let local_transform = position.transform() * orientation.transform();
let transform = global_transform * local_transform;
let render =
|src: PathEventSourceIter,
theme: &draw::Theme,
fill_tessellator: &mut lyon::tessellation::FillTessellator,
stroke_tessellator: &mut lyon::tessellation::StrokeTessellator| {
render_path_source(
src,
color,
transform,
options,
theme,
&draw::theme::Primitive::Path,
fill_tessellator,
stroke_tessellator,
mesh,
)
};
match path_event_src {
PathEventSource::Buffered(range) => {
let mut events = ctxt.path_event_buffer[range].iter().cloned();
let src = PathEventSourceIter::Events(&mut events);
render(
src,
&ctxt.theme,
&mut ctxt.fill_tessellator,
&mut ctxt.stroke_tessellator,
);
}
PathEventSource::ColoredPoints { range, close } => {
let mut points_colored = ctxt.path_points_colored_buffer[range].iter().cloned();
let src = PathEventSourceIter::ColoredPoints {
points: &mut points_colored,
close,
};
render(
src,
&ctxt.theme,
&mut ctxt.fill_tessellator,
&mut ctxt.stroke_tessellator,
);
}
PathEventSource::TexturedPoints { range, close } => {
let mut points_textured = ctxt.path_points_textured_buffer[range].iter().cloned();
let src = PathEventSourceIter::TexturedPoints {
points: &mut points_textured,
close,
};
render(
src,
&ctxt.theme,
&mut ctxt.fill_tessellator,
&mut ctxt.stroke_tessellator,
);
}
}
draw::renderer::PrimitiveRender {
texture_view,
vertex_mode,
}
}
}
pub fn points_colored_to_lyon_path<I>(points_colored: I, close: bool) -> Option<lyon::path::Path>
where
I: IntoIterator<Item = (Point2, Color)>,
{
let channels = draw::mesh::vertex::COLOR_CHANNEL_COUNT;
let mut path_builder = lyon::path::Path::builder_with_attributes(channels);
let mut iter = points_colored.into_iter();
let (first_point, first_color) = iter.next()?;
let p = first_point.into();
let (r, g, b, a) = first_color.into();
path_builder.move_to(p, &[r, g, b, a]);
for (point, color) in iter {
let p = point.into();
let (r, g, b, a) = color.into();
path_builder.line_to(p, &[r, g, b, a]);
}
if close {
path_builder.close();
}
Some(path_builder.build())
}
pub fn points_textured_to_lyon_path<I>(points_textured: I, close: bool) -> Option<lyon::path::Path>
where
I: IntoIterator<Item = (Point2, TexCoords)>,
{
let channels = 2;
let mut path_builder = lyon::path::Path::builder_with_attributes(channels);
let mut iter = points_textured.into_iter();
let (first_point, first_tex_coords) = iter.next()?;
let p = first_point.into();
let (tc_x, tc_y) = first_tex_coords.into();
path_builder.move_to(p, &[tc_x, tc_y]);
for (point, tex_coords) in iter {
let p = point.into();
let (tc_x, tc_y) = tex_coords.into();
path_builder.line_to(p, &[tc_x, tc_y]);
}
if close {
path_builder.close();
}
Some(path_builder.build())
}
impl<S> Path<S>
where
S: BaseFloat,
{
fn new(
position: position::Properties<S>,
orientation: orientation::Properties<S>,
color: Option<LinSrgba>,
path_event_src: PathEventSource,
options: Options,
vertex_mode: draw::renderer::VertexMode,
texture_view: Option<wgpu::TextureView>,
) -> Self {
Path {
color,
orientation,
position,
path_event_src,
options,
vertex_mode,
texture_view,
}
}
}
impl<'a, S> DrawingPathInit<'a, S>
where
S: BaseFloat,
{
pub fn fill(self) -> DrawingPathFill<'a, S> {
self.map_ty(|ty| ty.fill())
}
pub fn stroke(self) -> DrawingPathStroke<'a, S> {
self.map_ty(|ty| ty.stroke())
}
}
impl<'a, S> DrawingPathFill<'a, S>
where
S: BaseFloat,
{
pub fn tolerance(self, tolerance: f32) -> Self {
self.map_ty(|ty| ty.tolerance(tolerance))
}
pub fn rule(self, rule: lyon::tessellation::FillRule) -> Self {
self.map_ty(|ty| ty.rule(rule))
}
}
impl<'a, S> DrawingPathStroke<'a, S>
where
S: BaseFloat,
{
pub fn weight(self, weight: f32) -> Self {
self.map_ty(|ty| ty.stroke_weight(weight))
}
pub fn tolerance(self, tolerance: f32) -> Self {
self.map_ty(|ty| ty.stroke_tolerance(tolerance))
}
}
impl<'a, T, S> DrawingPathOptions<'a, T, S>
where
S: BaseFloat,
T: TessellationOptions,
PathOptions<T, S>: Into<Primitive<S>>,
Primitive<S>: Into<Option<PathOptions<T, S>>>,
{
pub fn events<I>(self, events: I) -> DrawingPath<'a, S>
where
I: IntoIterator<Item = lyon::path::PathEvent>,
{
self.map_ty_with_context(|ty, ctxt| ty.events(ctxt, events))
}
pub fn points<I>(self, points: I) -> DrawingPath<'a, S>
where
I: IntoIterator,
I::Item: Into<Point2<S>>,
{
self.map_ty_with_context(|ty, ctxt| ty.points(ctxt, points))
}
pub fn points_closed<I>(self, points: I) -> DrawingPath<'a, S>
where
I: IntoIterator,
I::Item: Into<Point2<S>>,
{
self.map_ty_with_context(|ty, ctxt| ty.points_closed(ctxt, points))
}
pub fn points_colored<I, P, C>(self, points: I) -> DrawingPath<'a, S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, C)>,
P: Into<Point2<S>>,
C: IntoLinSrgba<ColorScalar>,
{
self.map_ty_with_context(|ty, ctxt| ty.points_colored(ctxt, points))
}
pub fn points_colored_closed<I, P, C>(self, points: I) -> DrawingPath<'a, S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, C)>,
P: Into<Point2<S>>,
C: IntoLinSrgba<ColorScalar>,
{
self.map_ty_with_context(|ty, ctxt| ty.points_colored_closed(ctxt, points))
}
pub fn points_textured<I, P, TC>(
self,
view: &dyn wgpu::ToTextureView,
points: I,
) -> DrawingPath<'a, S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, TC)>,
P: Into<Point2<S>>,
TC: Into<TexCoords<S>>,
{
self.map_ty_with_context(|ty, ctxt| ty.points_textured(ctxt, view, points))
}
pub fn points_textured_closed<I, P, TC>(
self,
view: &dyn wgpu::ToTextureView,
points: I,
) -> DrawingPath<'a, S>
where
S: BaseFloat,
I: IntoIterator<Item = (P, TC)>,
P: Into<Point2<S>>,
TC: Into<TexCoords<S>>,
{
self.map_ty_with_context(|ty, ctxt| ty.points_textured_closed(ctxt, view, points))
}
}
impl<S> SetFill for PathFill<S> {
fn fill_options_mut(&mut self) -> &mut FillOptions {
&mut self.opts
}
}
impl<S> SetStroke for PathStroke<S> {
fn stroke_options_mut(&mut self) -> &mut StrokeOptions {
&mut self.opts
}
}
impl TessellationOptions for FillOptions {
type Tessellator = FillTessellator;
fn into_options(self) -> Options {
Options::Fill(self)
}
}
impl TessellationOptions for StrokeOptions {
type Tessellator = StrokeTessellator;
fn into_options(self) -> Options {
Options::Stroke(self)
}
}
impl<S> Default for PathInit<S> {
fn default() -> Self {
PathInit(std::marker::PhantomData)
}
}
impl<T, S> Default for PathOptions<T, S>
where
S: Zero,
T: Default,
{
fn default() -> Self {
let opts = Default::default();
PathOptions::new(opts)
}
}
impl<T, S> SetOrientation<S> for PathOptions<T, S> {
fn properties(&mut self) -> &mut orientation::Properties<S> {
SetOrientation::properties(&mut self.orientation)
}
}
impl<T, S> SetPosition<S> for PathOptions<T, S> {
fn properties(&mut self) -> &mut position::Properties<S> {
SetPosition::properties(&mut self.position)
}
}
impl<T, S> SetColor<ColorScalar> for PathOptions<T, S> {
fn rgba_mut(&mut self) -> &mut Option<LinSrgba> {
SetColor::rgba_mut(&mut self.color)
}
}
impl<S> SetOrientation<S> for Path<S> {
fn properties(&mut self) -> &mut orientation::Properties<S> {
SetOrientation::properties(&mut self.orientation)
}
}
impl<S> SetPosition<S> for Path<S> {
fn properties(&mut self) -> &mut position::Properties<S> {
SetPosition::properties(&mut self.position)
}
}
impl<S> SetColor<ColorScalar> for Path<S> {
fn rgba_mut(&mut self) -> &mut Option<LinSrgba> {
SetColor::rgba_mut(&mut self.color)
}
}
impl<S> From<PathInit<S>> for Primitive<S> {
fn from(prim: PathInit<S>) -> Self {
Primitive::PathInit(prim)
}
}
impl<S> From<PathStroke<S>> for Primitive<S> {
fn from(prim: PathStroke<S>) -> Self {
Primitive::PathStroke(prim)
}
}
impl<S> From<PathFill<S>> for Primitive<S> {
fn from(prim: PathFill<S>) -> Self {
Primitive::PathFill(prim)
}
}
impl<S> From<Path<S>> for Primitive<S> {
fn from(prim: Path<S>) -> Self {
Primitive::Path(prim)
}
}
impl<S> Into<Option<PathInit<S>>> for Primitive<S> {
fn into(self) -> Option<PathInit<S>> {
match self {
Primitive::PathInit(prim) => Some(prim),
_ => None,
}
}
}
impl<S> Into<Option<PathFill<S>>> for Primitive<S> {
fn into(self) -> Option<PathFill<S>> {
match self {
Primitive::PathFill(prim) => Some(prim),
_ => None,
}
}
}
impl<S> Into<Option<PathStroke<S>>> for Primitive<S> {
fn into(self) -> Option<PathStroke<S>> {
match self {
Primitive::PathStroke(prim) => Some(prim),
_ => None,
}
}
}
impl<S> Into<Option<Path<S>>> for Primitive<S> {
fn into(self) -> Option<Path<S>> {
match self {
Primitive::Path(prim) => Some(prim),
_ => None,
}
}
}