pub(crate) mod curve;
pub(crate) mod dynamic;
pub(crate) mod ellipse;
pub(crate) mod image;
pub(crate) mod text;
pub use self::image::*;
pub use curve::*;
pub use dynamic::*;
pub use ellipse::*;
use na::{Point2, Rotation2, Scale2, Vector2};
use nalgebra::{self as na, Transform2, Translation2};
use std::{fmt, marker::PhantomData, sync::Arc};
pub use text::*;
pub trait ShapeOp {
fn transform(&mut self, transform_matrix: Transform2<f32>) -> &mut Self;
#[inline]
fn translate<T: Into<Translation2<f32>>>(&mut self, translation: T) -> &mut Self {
self.transform(na::convert::<_, Transform2<f32>>(translation.into()));
self
}
#[inline]
fn scale<S: Into<Scale2<f32>>>(&mut self, scale: S) -> &mut Self {
self.transform(na::convert::<_, Transform2<f32>>(scale.into()));
self
}
#[inline]
fn rotate<R: Into<Rotation2<f32>>>(&mut self, rotation: R) -> &mut Self {
self.transform(na::convert::<_, Transform2<f32>>(rotation.into()));
self
}
fn local_transform(&self) -> &Transform2<f32>;
#[inline]
fn global_transform(&self, parent_transform: &Transform2<f32>) -> Transform2<f32> {
parent_transform * self.local_transform()
}
}
pub trait ShapeOpWith: ShapeOp + Sized {
#[inline]
fn with_transform(mut self, transform_matrix: Transform2<f32>) -> Self {
self.transform(transform_matrix);
self
}
#[inline]
fn with_translate<T: Into<Translation2<f32>>>(mut self, translation: T) -> Self {
self.translate(translation);
self
}
#[inline]
fn with_resize<S: Into<Scale2<f32>>>(mut self, scale: S) -> Self {
self.scale(scale);
self
}
#[inline]
fn with_rotate<R: Into<Rotation2<f32>>>(mut self, rotation: R) -> Self {
self.rotate(rotation);
self
}
}
impl<T: ShapeOp> ShapeOpWith for T {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnParticular;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Straight;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BoundingBox<Type> {
_ty: PhantomData<Type>,
top_left: Point2<f32>,
top_right: Point2<f32>,
bottom_right: Point2<f32>,
bottom_left: Point2<f32>,
}
impl<T> BoundingBox<T> {
#[inline]
pub fn top(&self) -> f32 {
self.top_left.y
}
#[inline]
pub fn bottom(&self) -> f32 {
self.bottom_left.y
}
#[inline]
pub fn left(&self) -> f32 {
self.bottom_left.x
}
#[inline]
pub fn right(&self) -> f32 {
self.bottom_right.x
}
#[inline]
pub fn top_left(&self) -> Point2<f32> {
self.top_left
}
#[inline]
pub fn top_right(&self) -> Point2<f32> {
self.top_right
}
#[inline]
pub fn bottom_right(&self) -> Point2<f32> {
self.bottom_right
}
#[inline]
pub fn bottom_left(&self) -> Point2<f32> {
self.bottom_left
}
pub fn transform(self, transform: &Transform2<f32>) -> BoundingBox<UnParticular> {
BoundingBox {
_ty: PhantomData,
top_left: transform * self.top_left,
top_right: transform * self.top_right,
bottom_right: transform * self.bottom_right,
bottom_left: transform * self.bottom_left,
}
}
pub fn width(&self) -> f32 {
(self.top_right - self.top_left).magnitude()
}
pub fn height(&self) -> f32 {
(self.top_right - self.bottom_right).magnitude()
}
}
impl BoundingBox<UnParticular> {
pub fn new(
top_left: Point2<f32>,
top_right: Point2<f32>,
bottom_right: Point2<f32>,
bottom_left: Point2<f32>,
) -> Self {
BoundingBox {
_ty: PhantomData,
top_left,
top_right,
bottom_right,
bottom_left,
}
}
pub fn center(&self) -> Point2<f32> {
let x =
(self.bottom_left.x + self.bottom_right.x + self.top_left.x + self.top_right.x) / 4.;
let y =
(self.bottom_left.y + self.bottom_right.y + self.top_left.y + self.top_right.y) / 4.;
Point2::new(x, y)
}
pub fn straigthen(&self) -> BoundingBox<Straight> {
let top = self
.top_left
.y
.max(self.top_right.y)
.max(self.bottom_left.y)
.max(self.bottom_right.y);
let bottom = self
.top_left
.y
.min(self.top_right.y)
.min(self.bottom_left.y)
.min(self.bottom_right.y);
let right = self
.top_left
.x
.max(self.top_right.x)
.max(self.bottom_left.x)
.max(self.bottom_right.x);
let left = self
.top_left
.x
.min(self.top_right.x)
.min(self.bottom_left.x)
.min(self.bottom_right.x);
BoundingBox {
_ty: PhantomData,
top_left: Point2::new(left, top),
top_right: Point2::new(right, top),
bottom_right: Point2::new(right, bottom),
bottom_left: Point2::new(left, bottom),
}
}
#[inline]
pub fn into_straight(self) -> BoundingBox<Straight> {
self.straigthen()
}
}
impl BoundingBox<Straight> {
pub fn zero() -> Self {
BoundingBox {
_ty: PhantomData,
top_left: Point2::origin(),
top_right: Point2::origin(),
bottom_right: Point2::origin(),
bottom_left: Point2::origin(),
}
}
pub fn at<P: Into<Point2<f32>>>(p: P) -> Self {
let p = p.into();
BoundingBox {
_ty: PhantomData,
top_left: p,
top_right: p,
bottom_right: p,
bottom_left: p,
}
}
pub fn mins_maxs(min_x: f32, min_y: f32, max_x: f32, max_y: f32) -> Self {
BoundingBox {
_ty: PhantomData,
top_left: [min_x, max_y].into(),
top_right: [max_x, max_y].into(),
bottom_right: [max_x, min_y].into(),
bottom_left: [min_x, min_y].into(),
}
}
pub fn centered<V: Into<Vector2<f32>>>(size: V) -> Self {
let size = size.into() / 2.;
BoundingBox {
_ty: PhantomData,
top_left: Point2::new(-size.x, size.y),
top_right: Point2::new(size.x, size.y),
bottom_right: Point2::new(size.x, -size.y),
bottom_left: Point2::new(-size.x, -size.y),
}
}
#[inline]
pub fn as_unparticular(self) -> BoundingBox<UnParticular> {
BoundingBox {
_ty: PhantomData,
top_left: self.top_left,
top_right: self.top_right,
bottom_right: self.bottom_right,
bottom_left: self.bottom_left,
}
}
pub fn straigthen(&self) -> BoundingBox<Straight> {
*self
}
#[inline]
pub fn into_straight(self) -> BoundingBox<Straight> {
self
}
#[inline]
pub fn scale_difference(&self, other: &BoundingBox<Straight>) -> Vector2<f32> {
Vector2::new(other.width() / self.width(), other.height() / self.height())
}
pub fn join(mut self, other: BoundingBox<Straight>) -> BoundingBox<Straight> {
let min_x = self.bottom_left.x.min(other.bottom_left.x);
let min_y = self.bottom_left.y.min(other.bottom_left.y);
let max_x = self.top_right.x.max(other.top_right.x);
let max_y = self.top_right.y.max(other.top_right.y);
self.top_left.x = min_x;
self.top_left.y = max_y;
self.top_right.x = max_x;
self.top_right.y = max_y;
self.bottom_right.x = max_x;
self.bottom_right.y = min_y;
self.bottom_left.x = min_x;
self.bottom_left.y = min_y;
self
}
pub fn intersect(mut self, other: BoundingBox<Straight>) -> BoundingBox<Straight> {
let (min_x, max_x) = if self.bottom_right.x <= other.bottom_left.x
|| self.bottom_left.x >= other.bottom_right.x
{
(0., 0.)
} else {
(
self.bottom_left.x.max(other.bottom_left.x),
self.top_right.x.min(other.top_right.x),
)
};
let (min_y, max_y) =
if self.top_left.y <= other.bottom_left.y || self.bottom_right.y >= other.top_left.y {
(0., 0.)
} else {
(
self.bottom_left.y.max(other.bottom_left.y),
self.top_right.y.min(other.top_right.y),
)
};
self.top_left.x = min_x;
self.top_left.y = max_y;
self.top_right.x = max_x;
self.top_right.y = max_y;
self.bottom_right.x = max_x;
self.bottom_right.y = min_y;
self.bottom_left.x = min_x;
self.bottom_left.y = min_y;
self
}
pub fn center(&self) -> Point2<f32> {
let x = (self.bottom_left.x + self.top_right.x) / 2.;
let y = (self.bottom_left.y + self.top_right.y) / 2.;
Point2::new(x, y)
}
}
pub trait ShapeBoundingBox {
fn local_bounding_box(&self) -> BoundingBox<UnParticular>;
fn global_bounding_box(&self, parent_transform: &Transform2<f32>) -> BoundingBox<UnParticular> {
self.local_bounding_box().transform(parent_transform)
}
}
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Group {
pub local_transform: Transform2<f32>,
pub shapes: Vec<Shape>,
pub metadata: Vec<(String, String)>,
}
#[derive(Clone)]
pub enum Shape {
Group(Group),
Style {
fill: Option<crate::style::Fill>,
stroke: Option<crate::style::Stroke>,
shape: Box<Shape>,
},
Ellipse(Ellipse),
Image(Image),
Text(Text),
Curve(Curve),
Dynamic {
local_transform: Transform2<f32>,
shaper: Arc<Shaper>,
},
}
impl PartialEq for Shape {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Shape::Group(g1), Shape::Group(g2)) => g1 == g2,
(
Shape::Style {
fill: fill1,
stroke: stroke1,
shape: shape1,
},
Shape::Style {
fill: fill2,
stroke: stroke2,
shape: shape2,
},
) => fill1 == fill2 && stroke1 == stroke2 && shape1 == shape2,
(Shape::Ellipse(e1), Shape::Ellipse(e2)) => e1 == e2,
(Shape::Image(i1), Shape::Image(i2)) => i1 == i2,
(Shape::Text(t1), Shape::Text(t2)) => t1 == t2,
(Shape::Curve(c1), Shape::Curve(c2)) => c1 == c2,
(
Shape::Dynamic {
local_transform: local_transform1,
shaper: shaper1,
},
Shape::Dynamic {
local_transform: local_transform2,
shaper: shaper2,
},
) => local_transform1 == local_transform2 && shaper1() == shaper2(),
_ => false,
}
}
}
impl Shape {
pub fn get_or_mutate_as_group(&mut self) -> &mut Group {
if let Shape::Group(g) = self {
g
} else {
let mut dummy = Shape::Group(Group {
local_transform: Default::default(),
shapes: Default::default(),
metadata: Default::default(),
});
std::mem::swap(self, &mut dummy);
let mut group = Shape::Group(Group {
local_transform: Default::default(),
shapes: vec![dummy],
metadata: vec![],
});
std::mem::swap(self, &mut group);
self.get_or_mutate_as_group()
}
}
pub fn extend_metadata<K: ToString, V: ToString, E: IntoIterator<Item = (K, V)>>(
&mut self,
extend: E,
) {
self.get_or_mutate_as_group().metadata.extend(
extend
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string())),
);
}
pub fn add_metadata<K: ToString, V: ToString>(&mut self, (key, value): (K, V)) {
let key = key.to_string();
let value = value.to_string();
self.get_or_mutate_as_group().metadata.push((key, value));
}
}
impl fmt::Debug for Shape {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Group(Group {
local_transform,
shapes,
metadata,
}) => f
.debug_struct("Group")
.field("local_transform", local_transform)
.field("shapes", shapes)
.field("metadata", metadata)
.finish(),
Self::Style {
fill,
stroke,
shape,
} => f
.debug_struct("Style")
.field("fill", fill)
.field("stroke", stroke)
.field("shape", shape)
.finish(),
Self::Ellipse(arg0) => f.debug_tuple("Ellipse").field(arg0).finish(),
Self::Image(arg0) => f.debug_tuple("Image").field(arg0).finish(),
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
Self::Curve(arg0) => f.debug_tuple("Curve").field(arg0).finish(),
Self::Dynamic {
local_transform,
shaper: _,
} => f
.debug_struct("Dynamic")
.field("local_transform", local_transform)
.field("shaper", &"Arc<Fn() -> Shape>")
.finish(),
}
}
}
impl Default for Shape {
fn default() -> Self {
Shape::Group(Group {
local_transform: Transform2::default(),
shapes: vec![],
metadata: vec![],
})
}
}
impl ShapeOp for Shape {
fn transform(&mut self, transform_matrix: Transform2<f32>) -> &mut Self {
match self {
Shape::Group(Group {
local_transform, ..
}) => {
*local_transform = transform_matrix * *local_transform;
}
Shape::Style { shape, .. } => {
shape.transform(transform_matrix);
}
Shape::Ellipse(v) => {
v.transform(transform_matrix);
}
Shape::Image(v) => {
v.transform(transform_matrix);
}
Shape::Text(v) => {
v.transform(transform_matrix);
}
Shape::Curve(v) => {
v.transform(transform_matrix);
}
Shape::Dynamic {
local_transform, ..
} => {
*local_transform = transform_matrix * *local_transform;
}
};
self
}
#[inline]
fn local_transform(&self) -> &Transform2<f32> {
match self {
Shape::Group(Group {
local_transform, ..
}) => local_transform,
Shape::Style { shape, .. } => shape.local_transform(),
Shape::Ellipse(v) => v.local_transform(),
Shape::Image(v) => v.local_transform(),
Shape::Text(v) => v.local_transform(),
Shape::Curve(v) => v.local_transform(),
Shape::Dynamic {
local_transform, ..
} => local_transform,
}
}
}
impl ShapeBoundingBox for Shape {
fn local_bounding_box(&self) -> BoundingBox<UnParticular> {
match self {
Shape::Group(Group {
local_transform,
shapes,
..
}) => shapes
.iter()
.map(|v| v.global_bounding_box(local_transform).straigthen())
.reduce(BoundingBox::join)
.unwrap_or_else(|| BoundingBox::zero())
.as_unparticular(),
Shape::Style { shape, .. } => shape.local_bounding_box(),
Shape::Ellipse(e) => e.local_bounding_box(),
Shape::Image(i) => i.local_bounding_box(),
Shape::Text(t) => t.local_bounding_box(),
Shape::Curve(c) => c.local_bounding_box(),
Shape::Dynamic {
local_transform,
shaper,
} => shaper().local_bounding_box().transform(local_transform),
}
}
}
#[cfg(test)]
mod tests {
use crate::prelude::*;
use nalgebra::{Point2, Rotation2, Transform2};
use std::f32::consts::FRAC_PI_2;
const EPS: f32 = 10e-6;
#[test]
fn parent_rotate_child_scale() {
let base = dessin!(Image(scale = [2., 4.], translate = [1., 2.]));
let base_position = base.position(&Transform2::default());
assert!(
(base_position.bottom_left - Point2::new(0., 0.)).magnitude() < EPS,
"left = {}, right = [0., 0.]",
base_position.bottom_left,
);
assert!(
(base_position.top_left - Point2::new(0., 4.)).magnitude() < EPS,
"left = {}, right = [0., 4.]",
base_position.top_left,
);
assert!(
(base_position.top_right - Point2::new(2., 4.)).magnitude() < EPS,
"left = {}, right = [2., 4.]",
base_position.top_right,
);
let transform = nalgebra::convert(Rotation2::new(FRAC_PI_2));
let transform_position = base.position(&transform);
assert!(
(transform_position.bottom_left - Point2::new(0., 0.)).magnitude() < EPS,
"left = {}, right = [0., 0.]",
transform_position.bottom_left,
);
assert!(
(transform_position.top_left - Point2::new(-4., 0.)).magnitude() < EPS,
"left = {}, right = [-4., 0.]",
transform_position.top_left,
);
assert!(
(transform_position.top_right - Point2::new(-4., 2.)).magnitude() < EPS,
"left = {}, right = [-4., 2.]",
transform_position.top_right,
);
}
}