mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 18:38:05 +08:00
Add a movable canvas with matricies (#175)
* Convert polygon and rectangle tool to kurbo::BezPath * Add glam * Add affine transform to elipse and remove circle * Format * Add svg group and add matrix for group * Convert all operations to use matricies * Work uses same transform as root * Format * Frontend fixed to render changes to working colors when changed from backend (#180) * Backend and Frontend modification to show working color mods * Remove comments & change precedence for tool and doc actions * Add keybind for resetting work colors * Minor Frontend changes * Remove early sample "greet" code * Add a contributing section to the project README * Add moving document around * Add document transform for tools * Update to GraphiteEditor's fork * Use write in foreach for rendering group / folder * Add missing TranslateDown action * Use points for line operation * Format * Add todo to change to shape's aspect ratio * Remove empty if * Initial pass at refactor * Fix polyline test * Use document message to modify document transform * Messages -> Operations * Transform layer * Format * Use DAffine2::IDENTITY * Clean up kurbo generation for line and rect * Use .into for rectangle points * Rename cols to transform * Rename other cols to transform * Add todo for into_iter * Remove unnecessary clone Co-authored-by: akshay1992kalbhor <akshay1992kalbhor@gmail.com> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
923e63c045
commit
bb3293af43
@@ -1,32 +0,0 @@
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Circle {
|
||||
shape: kurbo::Circle,
|
||||
style: style::PathStyle,
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub fn new(center: impl Into<kurbo::Point>, radius: f64, style: style::PathStyle) -> Circle {
|
||||
Circle {
|
||||
shape: kurbo::Circle::new(center, radius),
|
||||
style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for Circle {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<circle cx="{}" cy="{}" r="{}"{} />"#,
|
||||
self.shape.center.x,
|
||||
self.shape.center.y,
|
||||
self.shape.radius,
|
||||
self.style.render(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,24 @@
|
||||
use kurbo::Shape;
|
||||
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Ellipse {
|
||||
shape: kurbo::Ellipse,
|
||||
style: style::PathStyle,
|
||||
}
|
||||
pub struct Ellipse {}
|
||||
|
||||
impl Ellipse {
|
||||
pub fn new(center: impl Into<kurbo::Point>, radii: impl Into<kurbo::Vec2>, rotation: f64, style: style::PathStyle) -> Ellipse {
|
||||
Ellipse {
|
||||
shape: kurbo::Ellipse::new(center, radii, rotation),
|
||||
style,
|
||||
}
|
||||
pub fn new() -> Ellipse {
|
||||
Ellipse {}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for Ellipse {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
let kurbo::Vec2 { x: rx, y: ry } = self.shape.radii();
|
||||
let kurbo::Point { x: cx, y: cy } = self.shape.center();
|
||||
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<ellipse cx="0" cy="0" rx="{}" ry="{}" transform="translate({} {}) rotate({})"{} />"#,
|
||||
rx,
|
||||
ry,
|
||||
cx,
|
||||
cy,
|
||||
self.shape.rotation().to_degrees(),
|
||||
self.style.render(),
|
||||
);
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
|
||||
kurbo::Ellipse::from_affine(kurbo::Affine::new(transform.to_cols_array())).to_path(0.1)
|
||||
}
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{DocumentError, LayerId};
|
||||
|
||||
use super::{Layer, LayerData, LayerDataTypes};
|
||||
use super::{style, Layer, LayerData, LayerDataTypes};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -12,10 +12,21 @@ pub struct Folder {
|
||||
}
|
||||
|
||||
impl LayerData for Folder {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, _style: style::PathStyle) {
|
||||
let _ = writeln!(svg, r#"<g transform="matrix("#);
|
||||
transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
||||
let _ = svg.write_str(&(f.to_string() + if i != 5 { "," } else { "" }));
|
||||
});
|
||||
let _ = svg.write_str(r#")">"#);
|
||||
|
||||
for layer in &mut self.layers {
|
||||
let _ = writeln!(svg, "{}", layer.render());
|
||||
}
|
||||
let _ = writeln!(svg, "</g>");
|
||||
}
|
||||
|
||||
fn to_kurbo_path(&mut self, _: glam::DAffine2, _: style::PathStyle) -> kurbo::BezPath {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
use glam::DVec2;
|
||||
use kurbo::Point;
|
||||
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Line {
|
||||
shape: kurbo::Line,
|
||||
style: style::PathStyle,
|
||||
}
|
||||
pub struct Line {}
|
||||
|
||||
impl Line {
|
||||
pub fn new(p0: impl Into<kurbo::Point>, p1: impl Into<kurbo::Point>, style: style::PathStyle) -> Line {
|
||||
Line {
|
||||
shape: kurbo::Line::new(p0, p1),
|
||||
style,
|
||||
}
|
||||
pub fn new() -> Line {
|
||||
Line {}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for Line {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
let kurbo::Point { x: x1, y: y1 } = self.shape.p0;
|
||||
let kurbo::Point { x: x2, y: y2 } = self.shape.p1;
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
|
||||
fn new_point(a: DVec2) -> Point {
|
||||
Point::new(a.x, a.y)
|
||||
}
|
||||
let mut path = kurbo::BezPath::new();
|
||||
path.move_to(new_point(transform.translation));
|
||||
path.line_to(new_point(transform.transform_point2(DVec2::ONE)));
|
||||
path
|
||||
}
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
let [x1, y1] = transform.translation.to_array();
|
||||
let [x2, y2] = transform.transform_point2(DVec2::ONE).to_array();
|
||||
|
||||
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}"{} />"#, x1, y1, x2, y2, self.style.render(),);
|
||||
let _ = write!(svg, r#"<line x1="{}" y1="{}" x2="{}" y2="{}"{} />"#, x1, y1, x2, y2, style.render(),);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
pub mod style;
|
||||
|
||||
pub mod circle;
|
||||
pub use circle::Circle;
|
||||
|
||||
pub mod ellipse;
|
||||
pub use ellipse::Ellipse;
|
||||
|
||||
pub mod line;
|
||||
use kurbo::BezPath;
|
||||
pub use line::Line;
|
||||
|
||||
pub mod rect;
|
||||
@@ -21,14 +19,16 @@ pub use shape::Shape;
|
||||
pub mod folder;
|
||||
pub use folder::Folder;
|
||||
|
||||
use crate::DocumentError;
|
||||
|
||||
pub trait LayerData {
|
||||
fn render(&mut self, svg: &mut String);
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum LayerDataTypes {
|
||||
Folder(Folder),
|
||||
Circle(Circle),
|
||||
Ellipse(Ellipse),
|
||||
Rect(Rect),
|
||||
Line(Line),
|
||||
@@ -37,19 +37,36 @@ pub enum LayerDataTypes {
|
||||
}
|
||||
|
||||
macro_rules! call_render {
|
||||
($self:ident.render($svg:ident) { $($variant:ident),* }) => {
|
||||
($self:ident.render($svg:ident, $transform:ident, $style:ident) { $($variant:ident),* }) => {
|
||||
match $self {
|
||||
$(Self::$variant(x) => x.render($svg)),*
|
||||
$(Self::$variant(x) => x.render($svg, $transform, $style)),*
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! call_kurbo_path {
|
||||
($self:ident.to_kurbo_path($transform:ident, $style:ident) { $($variant:ident),* }) => {
|
||||
match $self {
|
||||
$(Self::$variant(x) => x.to_kurbo_path($transform, $style)),*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl LayerDataTypes {
|
||||
pub fn render(&mut self, svg: &mut String) {
|
||||
pub fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
call_render! {
|
||||
self.render(svg) {
|
||||
self.render(svg, transform, style) {
|
||||
Folder,
|
||||
Ellipse,
|
||||
Rect,
|
||||
Line,
|
||||
PolyLine,
|
||||
Shape
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn to_kurbo_path(&mut self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath {
|
||||
call_kurbo_path! {
|
||||
self.to_kurbo_path(transform, style) {
|
||||
Folder,
|
||||
Circle,
|
||||
Ellipse,
|
||||
Rect,
|
||||
Line,
|
||||
@@ -65,16 +82,20 @@ pub struct Layer {
|
||||
pub visible: bool,
|
||||
pub name: Option<String>,
|
||||
pub data: LayerDataTypes,
|
||||
pub transform: glam::DAffine2,
|
||||
pub style: style::PathStyle,
|
||||
pub cache: String,
|
||||
pub cache_dirty: bool,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(data: LayerDataTypes) -> Self {
|
||||
pub fn new(data: LayerDataTypes, transform: [f64; 6], style: style::PathStyle) -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
name: None,
|
||||
data,
|
||||
transform: glam::DAffine2::from_cols_array(&transform),
|
||||
style: style,
|
||||
cache: String::new(),
|
||||
cache_dirty: true,
|
||||
}
|
||||
@@ -86,9 +107,36 @@ impl Layer {
|
||||
}
|
||||
if self.cache_dirty {
|
||||
self.cache.clear();
|
||||
self.data.render(&mut self.cache);
|
||||
self.data.render(&mut self.cache, self.transform, self.style);
|
||||
self.cache_dirty = false;
|
||||
}
|
||||
self.cache.as_str()
|
||||
}
|
||||
|
||||
pub fn render_on(&mut self, svg: &mut String) {
|
||||
*svg += self.render();
|
||||
}
|
||||
|
||||
pub fn to_kurbo_path(&mut self) -> BezPath {
|
||||
self.data.to_kurbo_path(self.transform, self.style)
|
||||
}
|
||||
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
||||
match &mut self.data {
|
||||
LayerDataTypes::Folder(f) => Ok(f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
|
||||
match &self.data {
|
||||
LayerDataTypes::Folder(f) => Ok(&f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_as_folder(&mut self, svg: &mut String) {
|
||||
match &mut self.data {
|
||||
LayerDataTypes::Folder(f) => f.render(svg, self.transform, self.style),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
use super::{style, LayerData};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PolyLine {
|
||||
points: Vec<kurbo::Point>,
|
||||
style: style::PathStyle,
|
||||
points: Vec<glam::DVec2>,
|
||||
}
|
||||
|
||||
impl PolyLine {
|
||||
pub fn new(points: Vec<impl Into<kurbo::Point>>, style: style::PathStyle) -> PolyLine {
|
||||
pub fn new(points: Vec<impl Into<glam::DVec2>>) -> PolyLine {
|
||||
PolyLine {
|
||||
points: points.into_iter().map(|it| it.into()).collect(),
|
||||
style,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for PolyLine {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
|
||||
let mut path = kurbo::BezPath::new();
|
||||
self.points
|
||||
.iter()
|
||||
.map(|v| transform.transform_point2(*v))
|
||||
.map(|v| kurbo::Point { x: v.x, y: v.y })
|
||||
.enumerate()
|
||||
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
|
||||
path
|
||||
}
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
if self.points.is_empty() {
|
||||
return;
|
||||
}
|
||||
let _ = write!(svg, r#"<polyline points=""#);
|
||||
let mut points = self.points.iter();
|
||||
let mut points = self.points.iter().map(|v| transform.transform_point2(*v));
|
||||
let first = points.next().unwrap();
|
||||
let _ = write!(svg, "{:.3} {:.3}", first.x, first.y);
|
||||
for point in points {
|
||||
let _ = write!(svg, " {:.3} {:.3}", point.x, point.y);
|
||||
}
|
||||
let _ = write!(svg, r#""{} />"#, self.style.render());
|
||||
let _ = write!(svg, r#""{} />"#, style.render());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn polyline_should_render() {
|
||||
use super::style::PathStyle;
|
||||
use glam::DVec2;
|
||||
let mut polyline = PolyLine {
|
||||
points: vec![kurbo::Point::new(3.0, 4.12354), kurbo::Point::new(1.0, 5.54)],
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(crate::color::Color::GREEN, 0.4)), None),
|
||||
points: vec![DVec2::new(3.0, 4.12354), DVec2::new(1.0, 5.54)],
|
||||
};
|
||||
|
||||
let mut svg = String::new();
|
||||
polyline.render(&mut svg);
|
||||
assert_eq!(r##"<polyline points="3.000 4.124 1.000 5.540" stroke="#00FF00" stroke-width="0.4" />"##, svg);
|
||||
polyline.render(&mut svg, glam::DAffine2::IDENTITY, PathStyle::default());
|
||||
assert_eq!(r##"<polyline points="3.000 4.124 1.000 5.540" />"##, svg);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
use glam::DVec2;
|
||||
use kurbo::Point;
|
||||
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Rect {
|
||||
shape: kurbo::Rect,
|
||||
style: style::PathStyle,
|
||||
}
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Rect {}
|
||||
|
||||
impl Rect {
|
||||
pub fn new(p0: impl Into<kurbo::Point>, p1: impl Into<kurbo::Point>, style: style::PathStyle) -> Rect {
|
||||
Rect {
|
||||
shape: kurbo::Rect::from_points(p0, p1),
|
||||
style,
|
||||
}
|
||||
pub fn new() -> Rect {
|
||||
Rect {}
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for Rect {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<rect x="{}" y="{}" width="{}" height="{}"{} />"#,
|
||||
self.shape.min_x(),
|
||||
self.shape.min_y(),
|
||||
self.shape.width(),
|
||||
self.shape.height(),
|
||||
self.style.render(),
|
||||
);
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> kurbo::BezPath {
|
||||
fn new_point(a: DVec2) -> Point {
|
||||
Point::new(a.x, a.y)
|
||||
}
|
||||
let mut path = kurbo::BezPath::new();
|
||||
path.move_to(new_point(transform.translation));
|
||||
|
||||
// TODO: Use into_iter when new impls get added in rust 2021
|
||||
[(1., 0.), (1., 1.), (0., 1.)].iter().for_each(|v| path.line_to(new_point(transform.transform_point2((*v).into()))));
|
||||
path.close_path();
|
||||
path
|
||||
}
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,68 @@
|
||||
use crate::shape_points;
|
||||
use kurbo::BezPath;
|
||||
use kurbo::Vec2;
|
||||
|
||||
use super::style;
|
||||
use super::LayerData;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Shape {
|
||||
bounding_rect: kurbo::Rect,
|
||||
shape: shape_points::ShapePoints,
|
||||
style: style::PathStyle,
|
||||
equal_sides: bool,
|
||||
sides: u8,
|
||||
}
|
||||
|
||||
impl Shape {
|
||||
pub fn new(p0: impl Into<kurbo::Point>, p1: impl Into<kurbo::Point>, sides: u8, style: style::PathStyle) -> Shape {
|
||||
Shape {
|
||||
bounding_rect: kurbo::Rect::from_points(p0, p1),
|
||||
shape: shape_points::ShapePoints::new(kurbo::Point::new(0.5, 0.5), kurbo::Vec2::new(0.5, 0.0), sides),
|
||||
style,
|
||||
}
|
||||
pub fn new(equal_sides: bool, sides: u8) -> Shape {
|
||||
Shape { equal_sides, sides }
|
||||
}
|
||||
}
|
||||
|
||||
impl LayerData for Shape {
|
||||
fn render(&mut self, svg: &mut String) {
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<polygon points="{}" transform="translate({} {}) scale({} {})"{} />"#,
|
||||
self.shape,
|
||||
self.bounding_rect.origin().x,
|
||||
self.bounding_rect.origin().y,
|
||||
self.bounding_rect.width(),
|
||||
self.bounding_rect.height(),
|
||||
self.style.render(),
|
||||
);
|
||||
fn to_kurbo_path(&mut self, transform: glam::DAffine2, _style: style::PathStyle) -> BezPath {
|
||||
fn unit_rotation(theta: f64) -> Vec2 {
|
||||
Vec2::new(-theta.sin(), theta.cos())
|
||||
}
|
||||
let extent = Vec2::new((transform.x_axis.x + transform.x_axis.y) / 2., (transform.y_axis.x + transform.y_axis.y) / 2.);
|
||||
let translation = transform.translation;
|
||||
let mut path = kurbo::BezPath::new();
|
||||
let apothem_offset_angle = std::f64::consts::PI / (self.sides as f64);
|
||||
|
||||
let relative_points = (0..self.sides)
|
||||
.map(|i| apothem_offset_angle * ((i * 2 + ((self.sides + 1) % 2)) as f64))
|
||||
.map(|radians| unit_rotation(radians));
|
||||
|
||||
let (mut min_x, mut min_y, mut max_x, mut max_y) = (f64::MAX, f64::MAX, f64::MIN, f64::MIN);
|
||||
relative_points.clone().for_each(|p| {
|
||||
min_x = min_x.min(p.x);
|
||||
min_y = min_y.min(p.y);
|
||||
max_x = max_x.max(p.x);
|
||||
max_y = max_y.max(p.y);
|
||||
});
|
||||
|
||||
relative_points
|
||||
.map(|p| {
|
||||
if self.equal_sides {
|
||||
p
|
||||
} else {
|
||||
Vec2::new((p.x - min_x) / (max_x - min_x) * 2. - 1., (p.y - min_y) / (max_y - min_y) * 2. - 1.)
|
||||
}
|
||||
})
|
||||
.map(|unit| Vec2::new(-unit.x * extent.x + translation.x + extent.x, -unit.y * extent.y + translation.y + extent.y))
|
||||
.map(|pos| (pos).to_point())
|
||||
.enumerate()
|
||||
.for_each(|(i, p)| {
|
||||
if i == 0 {
|
||||
path.move_to(p);
|
||||
} else {
|
||||
path.line_to(p);
|
||||
}
|
||||
});
|
||||
|
||||
path.close_path();
|
||||
path
|
||||
}
|
||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle) {
|
||||
let _ = write!(svg, r#"<path d="{}" {} />"#, self.to_kurbo_path(transform, style).to_svg(), style.render());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user