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:
parent
923e63c045
commit
bb3293af43
|
|
@ -42,6 +42,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4126c0479ccf7e8664c36a2d719f5f2c140fbb4f9090008098d2c291fa5b3f16"
|
||||
|
||||
[[package]]
|
||||
name = "graphite-cli"
|
||||
version = "0.1.0"
|
||||
|
|
@ -50,6 +56,7 @@ version = "0.1.0"
|
|||
name = "graphite-document-core"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"glam",
|
||||
"kurbo",
|
||||
"log",
|
||||
"serde",
|
||||
|
|
@ -60,6 +67,7 @@ name = "graphite-editor-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"glam",
|
||||
"graphite-document-core",
|
||||
"graphite-proc-macros",
|
||||
"log",
|
||||
|
|
@ -110,9 +118,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e30b1df631d23875f230ed3ddd1a88c231f269a04b2044eb6ca87e763b5f4c42"
|
||||
version = "0.8.3"
|
||||
source = "git+https://github.com/linebender/kurbo.git#9ed4b73dac4f085065d7a6968121581cb8296089"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -10,5 +10,6 @@ license = "Apache-2.0"
|
|||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
kurbo = "0.8.0"
|
||||
kurbo = {git="https://github.com/linebender/kurbo.git"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
glam = "0.16"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use glam::DAffine2;
|
||||
|
||||
use crate::{
|
||||
layers::{self, Folder, Layer, LayerData, LayerDataTypes, Line, PolyLine, Rect, Shape},
|
||||
layers::{self, style::PathStyle, Folder, Layer, LayerDataTypes, Line, PolyLine, Rect, Shape},
|
||||
DocumentError, DocumentResponse, LayerId, Operation,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Document {
|
||||
pub root: layers::Folder,
|
||||
pub work: Folder,
|
||||
pub root: Layer,
|
||||
pub work: Layer,
|
||||
pub work_mount_path: Vec<LayerId>,
|
||||
pub work_operations: Vec<Operation>,
|
||||
pub work_mounted: bool,
|
||||
|
|
@ -15,8 +17,8 @@ pub struct Document {
|
|||
impl Default for Document {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
root: Folder::default(),
|
||||
work: Folder::default(),
|
||||
root: Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()),
|
||||
work: Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()),
|
||||
work_mount_path: Vec::new(),
|
||||
work_operations: Vec::new(),
|
||||
work_mounted: false,
|
||||
|
|
@ -41,8 +43,11 @@ impl Document {
|
|||
return;
|
||||
}
|
||||
if path.as_slice() == self.work_mount_path {
|
||||
self.document_folder_mut(path).unwrap().render(svg);
|
||||
self.work.render(svg);
|
||||
// TODO: Handle if mounted in nested folders
|
||||
let transform = self.document_folder(path).unwrap().transform;
|
||||
self.document_folder_mut(path).unwrap().render_as_folder(svg);
|
||||
self.work.transform = transform;
|
||||
self.work.render_as_folder(svg);
|
||||
path.pop();
|
||||
}
|
||||
let ids = self.folder(path).unwrap().layer_ids.clone();
|
||||
|
|
@ -68,10 +73,10 @@ impl Document {
|
|||
/// This function respects mounted folders and will thus not contain the layers already
|
||||
/// present in the document if a temporary folder is mounted on top.
|
||||
pub fn folder(&self, mut path: &[LayerId]) -> Result<&Folder, DocumentError> {
|
||||
let mut root = &self.root;
|
||||
let mut root = self.root.as_folder()?;
|
||||
if self.is_mounted(self.work_mount_path.as_slice(), path) {
|
||||
path = &path[self.work_mount_path.len()..];
|
||||
root = &self.work;
|
||||
root = self.work.as_folder()?;
|
||||
}
|
||||
for id in path {
|
||||
root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
|
|
@ -87,9 +92,9 @@ impl Document {
|
|||
pub fn folder_mut(&mut self, mut path: &[LayerId]) -> Result<&mut Folder, DocumentError> {
|
||||
let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) {
|
||||
path = &path[self.work_mount_path.len()..];
|
||||
&mut self.work
|
||||
self.work.as_folder_mut()?
|
||||
} else {
|
||||
&mut self.root
|
||||
self.root.as_folder_mut()?
|
||||
};
|
||||
for id in path {
|
||||
root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
|
|
@ -101,10 +106,10 @@ impl Document {
|
|||
/// or if the requested layer is not of type folder.
|
||||
/// This function does **not** respect mounted folders and will always return the current
|
||||
/// state of the document, disregarding any temporary modifications.
|
||||
pub fn document_folder(&self, path: &[LayerId]) -> Result<&Folder, DocumentError> {
|
||||
pub fn document_folder(&self, path: &[LayerId]) -> Result<&Layer, DocumentError> {
|
||||
let mut root = &self.root;
|
||||
for id in path {
|
||||
root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
root = root.as_folder()?.layer(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
|
|
@ -114,10 +119,10 @@ impl Document {
|
|||
/// This function does **not** respect mounted folders and will always return the current
|
||||
/// state of the document, disregarding any temporary modifications.
|
||||
/// If you manually edit the folder you have to set the cache_dirty flag yourself.
|
||||
pub fn document_folder_mut(&mut self, path: &[LayerId]) -> Result<&mut Folder, DocumentError> {
|
||||
pub fn document_folder_mut(&mut self, path: &[LayerId]) -> Result<&mut Layer, DocumentError> {
|
||||
let mut root = &mut self.root;
|
||||
for id in path {
|
||||
root = root.folder_mut(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
root = root.as_folder_mut()?.layer_mut(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||
}
|
||||
Ok(root)
|
||||
}
|
||||
|
|
@ -137,7 +142,7 @@ impl Document {
|
|||
|
||||
/// Replaces the layer at the specified `path` with `layer`.
|
||||
pub fn set_layer(&mut self, path: &[LayerId], layer: Layer) -> Result<(), DocumentError> {
|
||||
let mut folder = &mut self.root;
|
||||
let mut folder = self.root.as_folder_mut()?;
|
||||
if let Ok((path, id)) = split_path(path) {
|
||||
self.layer_mut(path)?.cache_dirty = true;
|
||||
folder = self.folder_mut(path)?;
|
||||
|
|
@ -163,7 +168,7 @@ impl Document {
|
|||
pub fn delete(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
|
||||
let (path, id) = split_path(path)?;
|
||||
let _ = self.layer_mut(path).map(|x| x.cache_dirty = true);
|
||||
self.document_folder_mut(path)?.remove_layer(id)?;
|
||||
self.document_folder_mut(path)?.as_folder_mut()?.remove_layer(id)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -171,73 +176,46 @@ impl Document {
|
|||
/// reaction from the frontend, responses may be returned.
|
||||
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
|
||||
let responses = match &operation {
|
||||
Operation::AddCircle { path, insert_index, cx, cy, r, style } => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Circle(layers::Circle::new((*cx, *cy), *r, *style))), *insert_index)?;
|
||||
Operation::AddEllipse { path, insert_index, transform, style } => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new()), *transform, *style), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddEllipse {
|
||||
Operation::AddRect { path, insert_index, transform, style } => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new()), *transform, *style), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddLine { path, insert_index, transform, style } => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new()), *transform, *style), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddPen {
|
||||
path,
|
||||
insert_index,
|
||||
cx,
|
||||
cy,
|
||||
rx,
|
||||
ry,
|
||||
rot,
|
||||
points,
|
||||
transform,
|
||||
style,
|
||||
} => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Ellipse(layers::Ellipse::new((*cx, *cy), (*rx, *ry), *rot, *style))), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddRect {
|
||||
path,
|
||||
insert_index,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style,
|
||||
} => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Rect(Rect::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddLine {
|
||||
path,
|
||||
insert_index,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
style,
|
||||
} => {
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Line(Line::new((*x0, *y0), (*x1, *y1), *style))), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
}
|
||||
Operation::AddPen { path, insert_index, points, style } => {
|
||||
let points: Vec<kurbo::Point> = points.iter().map(|&it| it.into()).collect();
|
||||
let polyline = PolyLine::new(points, *style);
|
||||
self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline)), *insert_index)?;
|
||||
let points: Vec<glam::DVec2> = points.iter().map(|&it| it.into()).collect();
|
||||
let polyline = PolyLine::new(points);
|
||||
self.add_layer(&path, Layer::new(LayerDataTypes::PolyLine(polyline), *transform, *style), *insert_index)?;
|
||||
Some(vec![DocumentResponse::DocumentChanged])
|
||||
}
|
||||
Operation::AddShape {
|
||||
path,
|
||||
insert_index,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
transform,
|
||||
equal_sides,
|
||||
sides,
|
||||
style,
|
||||
} => {
|
||||
let s = Shape::new((*x0, *y0), (*x1, *y1), *sides, *style);
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s)), *insert_index)?;
|
||||
let s = Shape::new(*equal_sides, *sides);
|
||||
let id = self.add_layer(&path, Layer::new(LayerDataTypes::Shape(s), *transform, *style), *insert_index)?;
|
||||
let path = [path.clone(), vec![id]].concat();
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::SelectLayer { path }])
|
||||
|
|
@ -256,27 +234,34 @@ impl Document {
|
|||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: folder_path.to_vec() }])
|
||||
}
|
||||
Operation::AddFolder { path } => {
|
||||
self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default())))?;
|
||||
self.set_layer(&path, Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default()))?;
|
||||
|
||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.clone() }])
|
||||
}
|
||||
Operation::MountWorkingFolder { path } => {
|
||||
self.work_mount_path = path.clone();
|
||||
self.work_operations.clear();
|
||||
self.work = Folder::default();
|
||||
self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default());
|
||||
self.work_mounted = true;
|
||||
None
|
||||
}
|
||||
Operation::TransformLayer { path, transform } => {
|
||||
let transform = self.root.transform * DAffine2::from_cols_array(&transform);
|
||||
let layer = self.document_folder_mut(path).unwrap();
|
||||
layer.transform = transform;
|
||||
layer.cache_dirty = true;
|
||||
Some(vec![DocumentResponse::DocumentChanged])
|
||||
}
|
||||
Operation::DiscardWorkingFolder => {
|
||||
self.work_operations.clear();
|
||||
self.work_mount_path = vec![];
|
||||
self.work = Folder::default();
|
||||
self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default());
|
||||
self.work_mounted = false;
|
||||
Some(vec![DocumentResponse::DocumentChanged])
|
||||
}
|
||||
Operation::ClearWorkingFolder => {
|
||||
self.work_operations.clear();
|
||||
self.work = Folder::default();
|
||||
self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default());
|
||||
Some(vec![DocumentResponse::DocumentChanged])
|
||||
}
|
||||
Operation::CommitTransaction => {
|
||||
|
|
@ -286,7 +271,7 @@ impl Document {
|
|||
std::mem::swap(&mut ops, &mut self.work_operations);
|
||||
self.work_mounted = false;
|
||||
self.work_mount_path = vec![];
|
||||
self.work = Folder::default();
|
||||
self.work = Layer::new(LayerDataTypes::Folder(Folder::default()), DAffine2::IDENTITY.to_cols_array(), PathStyle::default());
|
||||
let mut responses = vec![];
|
||||
for operation in ops.into_iter() {
|
||||
if let Some(mut op_responses) = self.handle_operation(operation)? {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ pub mod document;
|
|||
pub mod layers;
|
||||
pub mod operation;
|
||||
pub mod response;
|
||||
mod shape_points;
|
||||
|
||||
pub use operation::Operation;
|
||||
pub use response::DocumentResponse;
|
||||
|
|
@ -15,4 +14,5 @@ pub enum DocumentError {
|
|||
LayerNotFound,
|
||||
InvalidPath,
|
||||
IndexOutOfBounds,
|
||||
NotAFolder,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,44 +5,27 @@ use serde::{Deserialize, Serialize};
|
|||
#[repr(C)]
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum Operation {
|
||||
AddCircle {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
cx: f64,
|
||||
cy: f64,
|
||||
r: f64,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddEllipse {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
cx: f64,
|
||||
cy: f64,
|
||||
rx: f64,
|
||||
ry: f64,
|
||||
rot: f64,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddRect {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
x0: f64,
|
||||
y0: f64,
|
||||
x1: f64,
|
||||
y1: f64,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddLine {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
x0: f64,
|
||||
y0: f64,
|
||||
x1: f64,
|
||||
y1: f64,
|
||||
transform: [f64; 6],
|
||||
style: style::PathStyle,
|
||||
},
|
||||
AddPen {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
insert_index: isize,
|
||||
points: Vec<(f64, f64)>,
|
||||
style: style::PathStyle,
|
||||
|
|
@ -50,10 +33,8 @@ pub enum Operation {
|
|||
AddShape {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
x0: f64,
|
||||
y0: f64,
|
||||
x1: f64,
|
||||
y1: f64,
|
||||
transform: [f64; 6],
|
||||
equal_sides: bool,
|
||||
sides: u8,
|
||||
style: style::PathStyle,
|
||||
},
|
||||
|
|
@ -69,6 +50,10 @@ pub enum Operation {
|
|||
MountWorkingFolder {
|
||||
path: Vec<LayerId>,
|
||||
},
|
||||
TransformLayer {
|
||||
path: Vec<LayerId>,
|
||||
transform: [f64; 6],
|
||||
},
|
||||
DiscardWorkingFolder,
|
||||
ClearWorkingFolder,
|
||||
CommitTransaction,
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
use std::{fmt, ops::Add};
|
||||
|
||||
use kurbo::{PathEl, Point, Vec2};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ShapePoints {
|
||||
center: kurbo::Point,
|
||||
extent: kurbo::Vec2,
|
||||
sides: u8,
|
||||
}
|
||||
|
||||
impl ShapePoints {
|
||||
/// A new shape from center, a point and the number of points.
|
||||
#[inline]
|
||||
pub fn new(center: impl Into<Point>, extent: impl Into<Vec2>, sides: u8) -> ShapePoints {
|
||||
ShapePoints {
|
||||
center: center.into(),
|
||||
extent: extent.into(),
|
||||
sides,
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the angle in radians between the longest line from the center and the apothem.
|
||||
#[inline]
|
||||
pub fn apothem_offset_angle(&self) -> f64 {
|
||||
std::f64::consts::PI / (self.sides as f64)
|
||||
}
|
||||
|
||||
// Gets the apothem (the shortest distance from the center to the edge)
|
||||
#[inline]
|
||||
pub fn apothem(&self) -> f64 {
|
||||
self.apothem_offset_angle().cos() * (self.sides as f64)
|
||||
}
|
||||
|
||||
// Gets the length of one side
|
||||
#[inline]
|
||||
pub fn side_length(&self) -> f64 {
|
||||
self.apothem_offset_angle().sin() * (self.sides as f64) * 2f64
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: The display impl and iter impl share large amounts of code and should be refactored. (Display should use the Iterator)
|
||||
// TODO: Once that is done, the trailing space from the display impl should be removed
|
||||
// Also consider implementing index
|
||||
impl std::fmt::Display for ShapePoints {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fn rotate(v: &Vec2, theta: f64) -> Vec2 {
|
||||
let cosine = theta.cos();
|
||||
let sine = theta.sin();
|
||||
Vec2::new(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine)
|
||||
}
|
||||
for i in 0..self.sides {
|
||||
let radians = self.apothem_offset_angle() * ((i * 2 + (self.sides % 2)) as f64);
|
||||
let offset = rotate(&self.extent, radians);
|
||||
let point = self.center + offset;
|
||||
write!(f, "{},{} ", point.x, point.y)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct ShapePathIter {
|
||||
shape: ShapePoints,
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Iterator for ShapePathIter {
|
||||
type Item = PathEl;
|
||||
|
||||
fn next(&mut self) -> Option<PathEl> {
|
||||
fn rotate(v: &Vec2, theta: f64) -> Vec2 {
|
||||
let cosine = theta.cos();
|
||||
let sine = theta.sin();
|
||||
Vec2::new(v.x * cosine - v.y * sine, v.x * sine + v.y * cosine)
|
||||
}
|
||||
self.index += 1;
|
||||
match self.index {
|
||||
1 => Some(PathEl::MoveTo(self.shape.center + self.shape.extent)),
|
||||
_ => {
|
||||
let radians = self.shape.apothem_offset_angle() * ((self.index * 2 + (self.shape.sides % 2) as usize) as f64);
|
||||
let offset = rotate(&self.shape.extent, radians);
|
||||
let point = self.shape.center + offset;
|
||||
Some(PathEl::LineTo(point))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vec2> for ShapePoints {
|
||||
type Output = ShapePoints;
|
||||
|
||||
#[inline]
|
||||
fn add(self, movement: Vec2) -> ShapePoints {
|
||||
ShapePoints {
|
||||
center: self.center + movement,
|
||||
extent: self.extent,
|
||||
sides: self.sides,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl kurbo::Shape for ShapePoints {
|
||||
type PathElementsIter = ShapePathIter;
|
||||
|
||||
fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn area(&self) -> f64 {
|
||||
self.apothem() * self.perimeter(2.1)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn perimeter(&self, _accuracy: f64) -> f64 {
|
||||
self.side_length() * (self.sides as f64)
|
||||
}
|
||||
|
||||
fn winding(&self, _pt: Point) -> i32 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn bounding_box(&self) -> kurbo::Rect {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@ bitflags = "1.2.1"
|
|||
thiserror = "1.0.24"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
graphite-proc-macros = {path = "../proc-macro"}
|
||||
glam = "0.16"
|
||||
|
||||
[dependencies.document-core]
|
||||
path = "../document"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ impl Dispatcher {
|
|||
}
|
||||
match message {
|
||||
NoOp => (),
|
||||
Document(message) => self.document_message_handler.process_action(message, (), &mut self.messages),
|
||||
Document(message) => self.document_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
|
||||
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
|
||||
Tool(message) => self
|
||||
.tool_message_handler
|
||||
|
|
|
|||
|
|
@ -60,9 +60,10 @@ impl Document {
|
|||
let folder = self.document.document_folder(path)?;
|
||||
let self_layer_data = &mut self.layer_data;
|
||||
let entries = folder
|
||||
.as_folder()?
|
||||
.layers()
|
||||
.iter()
|
||||
.zip(folder.layer_ids.iter())
|
||||
.zip(folder.as_folder()?.layer_ids.iter())
|
||||
.rev()
|
||||
.map(|(layer, id)| {
|
||||
let path = [path, &[*id]].concat();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
use crate::message_prelude::*;
|
||||
use crate::{
|
||||
input::{mouse::ViewportPosition, InputPreprocessor},
|
||||
message_prelude::*,
|
||||
};
|
||||
use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use log::info;
|
||||
|
||||
use crate::document::Document;
|
||||
use std::collections::VecDeque;
|
||||
|
|
@ -25,6 +30,9 @@ pub enum DocumentMessage {
|
|||
ExportDocument,
|
||||
RenderDocument,
|
||||
Undo,
|
||||
MouseMove,
|
||||
TranslateDown,
|
||||
TranslateUp,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
|
|
@ -42,6 +50,8 @@ impl From<DocumentOperation> for Message {
|
|||
pub struct DocumentMessageHandler {
|
||||
documents: Vec<Document>,
|
||||
active_document: usize,
|
||||
mmb_down: bool,
|
||||
mouse_pos: ViewportPosition,
|
||||
}
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
|
|
@ -78,12 +88,14 @@ impl Default for DocumentMessageHandler {
|
|||
Self {
|
||||
documents: vec![Document::default()],
|
||||
active_document: 0,
|
||||
mmb_down: false,
|
||||
mouse_pos: ViewportPosition::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
|
||||
fn process_action(&mut self, message: DocumentMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
match message {
|
||||
DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()),
|
||||
|
|
@ -224,7 +236,7 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
|
|||
}
|
||||
Undo => {
|
||||
// this is a temporary fix and will be addressed by #123
|
||||
if let Some(id) = self.active_document().document.root.list_layers().last() {
|
||||
if let Some(id) = self.active_document().document.root.as_folder().unwrap().list_layers().last() {
|
||||
responses.push_back(DocumentOperation::DeleteLayer { path: vec![*id] }.into())
|
||||
}
|
||||
}
|
||||
|
|
@ -259,14 +271,32 @@ impl MessageHandler<DocumentMessage, ()> for DocumentMessageHandler {
|
|||
}
|
||||
.into(),
|
||||
),
|
||||
TranslateDown => {
|
||||
self.mmb_down = true;
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
}
|
||||
TranslateUp => {
|
||||
self.mmb_down = false;
|
||||
}
|
||||
MouseMove => {
|
||||
if self.mmb_down {
|
||||
let delta = DVec2::new(ipp.mouse.position.x as f64 - self.mouse_pos.x as f64, ipp.mouse.position.y as f64 - self.mouse_pos.y as f64);
|
||||
let operation = DocumentOperation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: DAffine2::from_translation(delta).to_cols_array(),
|
||||
};
|
||||
responses.push_back(operation.into());
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
}
|
||||
}
|
||||
message => todo!("document_action_handler does not implement: {}", message.to_discriminant().global_name()),
|
||||
}
|
||||
}
|
||||
fn actions(&self) -> ActionList {
|
||||
if self.active_document().layer_data.values().any(|data| data.selected) {
|
||||
actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument)
|
||||
actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown)
|
||||
} else {
|
||||
actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument)
|
||||
actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ impl From<&LayerDataTypes> for LayerType {
|
|||
match data {
|
||||
Folder(_) => LayerType::Folder,
|
||||
Shape(_) => LayerType::Shape,
|
||||
Circle(_) => LayerType::Circle,
|
||||
Rect(_) => LayerType::Rect,
|
||||
Line(_) => LayerType::Line,
|
||||
PolyLine(_) => LayerType::PolyLine,
|
||||
|
|
|
|||
|
|
@ -168,6 +168,9 @@ impl Default for Mapping {
|
|||
entry! {action=DocumentMessage::DeleteSelectedLayers, key_down=KeyBackspace},
|
||||
entry! {action=DocumentMessage::ExportDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=DocumentMessage::TranslateDown, key_down=Mmb},
|
||||
entry! {action=DocumentMessage::TranslateUp, key_up=Mmb},
|
||||
entry! {action=DocumentMessage::NewDocument, key_down=KeyN, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::NextDocument, key_down=KeyTab, modifiers=[KeyShift]},
|
||||
entry! {action=DocumentMessage::CloseActiveDocument, key_down=KeyW, modifiers=[KeyShift]},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
|||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Ellipse {
|
||||
|
|
@ -58,7 +59,8 @@ struct EllipseToolData {
|
|||
impl Fsm for EllipseToolFsmState {
|
||||
type ToolData = EllipseToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
let transform = document.root.transform;
|
||||
use EllipseMessage::*;
|
||||
use EllipseToolFsmState::*;
|
||||
if let ToolMessage::Ellipse(event) = event {
|
||||
|
|
@ -73,7 +75,7 @@ impl Fsm for EllipseToolFsmState {
|
|||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
Dragging
|
||||
}
|
||||
|
|
@ -83,7 +85,7 @@ impl Fsm for EllipseToolFsmState {
|
|||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
|
|
@ -97,13 +99,13 @@ impl Fsm for EllipseToolFsmState {
|
|||
}
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_circle, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_circle, false, tool_data, data, responses, Dragging, transform),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -124,16 +126,17 @@ fn update_state(
|
|||
data: &mut EllipseToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: EllipseToolFsmState,
|
||||
transform: DAffine2,
|
||||
) -> EllipseToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(&data, tool_data));
|
||||
responses.push_back(make_operation(&data, tool_data, transform));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Message {
|
||||
fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
|
|
@ -147,12 +150,10 @@ fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Messa
|
|||
let (x2, y2) = (x0 + (x1 - x0).signum() * diameter, y0 + (y1 - y0).signum() * diameter);
|
||||
((x0 + x2) * 0.5, (y0 + y2) * 0.5, diameter * 0.5)
|
||||
};
|
||||
Operation::AddCircle {
|
||||
Operation::AddEllipse {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
r,
|
||||
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(r, r), 0., DVec2::new(cx, cy))).to_cols_array(),
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
} else {
|
||||
|
|
@ -161,11 +162,7 @@ fn make_operation(data: &EllipseToolData, tool_data: &DocumentToolData) -> Messa
|
|||
Operation::AddEllipse {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
cx,
|
||||
cy,
|
||||
rx,
|
||||
ry,
|
||||
rot: 0.0,
|
||||
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(rx, ry), 0., DVec2::new(cx, cy))).to_cols_array(),
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
|||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
|
|
@ -63,7 +64,8 @@ struct LineToolData {
|
|||
impl Fsm for LineToolFsmState {
|
||||
type ToolData = LineToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
let transform = document.root.transform;
|
||||
use LineMessage::*;
|
||||
use LineToolFsmState::*;
|
||||
if let ToolMessage::Line(event) = event {
|
||||
|
|
@ -80,7 +82,7 @@ impl Fsm for LineToolFsmState {
|
|||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
Dragging
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ impl Fsm for LineToolFsmState {
|
|||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
|
|
@ -104,18 +106,18 @@ impl Fsm for LineToolFsmState {
|
|||
}
|
||||
(Ready, LockAngle) => update_state_no_op(&mut data.lock_angle, true, Ready),
|
||||
(Ready, UnlockAngle) => update_state_no_op(&mut data.lock_angle, false, Ready),
|
||||
(Dragging, LockAngle) => update_state(|data| &mut data.lock_angle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAngle) => update_state(|data| &mut data.lock_angle, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, LockAngle) => update_state(|data| &mut data.lock_angle, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnlockAngle) => update_state(|data| &mut data.lock_angle, false, tool_data, data, responses, Dragging, transform),
|
||||
|
||||
(Ready, SnapToAngle) => update_state_no_op(&mut data.snap_angle, true, Ready),
|
||||
(Ready, UnSnapToAngle) => update_state_no_op(&mut data.snap_angle, false, Ready),
|
||||
(Dragging, SnapToAngle) => update_state(|data| &mut data.snap_angle, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnSnapToAngle) => update_state(|data| &mut data.snap_angle, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, SnapToAngle) => update_state(|data| &mut data.snap_angle, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnSnapToAngle) => update_state(|data| &mut data.snap_angle, false, tool_data, data, responses, Dragging, transform),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -136,16 +138,17 @@ fn update_state(
|
|||
data: &mut LineToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: LineToolFsmState,
|
||||
transform: DAffine2,
|
||||
) -> LineToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Message {
|
||||
fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
|
|
@ -174,10 +177,7 @@ fn make_operation(data: &mut LineToolData, tool_data: &DocumentToolData) -> Mess
|
|||
Operation::AddLine {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), None),
|
||||
}
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Pen {
|
||||
|
|
@ -46,14 +47,17 @@ impl Default for PenToolFsmState {
|
|||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PenToolData {
|
||||
points: Vec<ViewportPosition>,
|
||||
next_point: ViewportPosition,
|
||||
points: Vec<DAffine2>,
|
||||
next_point: DAffine2,
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
let transform = document.root.transform;
|
||||
let pos = transform.inverse() * DAffine2::from_translation(DVec2::new(input.mouse.position.x as f64, input.mouse.position.y as f64));
|
||||
|
||||
use PenMessage::*;
|
||||
use PenToolFsmState::*;
|
||||
if let ToolMessage::Pen(event) = event {
|
||||
|
|
@ -61,16 +65,16 @@ impl Fsm for PenToolFsmState {
|
|||
(Ready, DragStart) => {
|
||||
responses.push_back(Operation::MountWorkingFolder { path: vec![] }.into());
|
||||
|
||||
data.points.push(input.mouse.position);
|
||||
data.next_point = input.mouse.position;
|
||||
data.points.push(pos);
|
||||
data.next_point = pos;
|
||||
|
||||
Dragging
|
||||
}
|
||||
(Dragging, DragStop) => {
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.points.last() != Some(&input.mouse.position) {
|
||||
data.points.push(input.mouse.position);
|
||||
data.next_point = input.mouse.position;
|
||||
if data.points.last() != Some(&pos) {
|
||||
data.points.push(pos);
|
||||
data.next_point = pos;
|
||||
}
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
|
|
@ -79,7 +83,7 @@ impl Fsm for PenToolFsmState {
|
|||
Dragging
|
||||
}
|
||||
(Dragging, MouseMove) => {
|
||||
data.next_point = input.mouse.position;
|
||||
data.next_point = pos;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data, true));
|
||||
|
|
@ -116,13 +120,14 @@ impl Fsm for PenToolFsmState {
|
|||
}
|
||||
|
||||
fn make_operation(data: &PenToolData, tool_data: &DocumentToolData, show_preview: bool) -> Message {
|
||||
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x as f64, p.y as f64)).collect();
|
||||
let mut points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.translation.x, p.translation.y)).collect();
|
||||
if show_preview {
|
||||
points.push((data.next_point.x as f64, data.next_point.y as f64))
|
||||
points.push((data.next_point.translation.x, data.next_point.translation.y))
|
||||
}
|
||||
Operation::AddPen {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, 5.)), Some(style::Fill::none())),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
|||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Rectangle {
|
||||
|
|
@ -57,7 +58,8 @@ struct RectangleToolData {
|
|||
impl Fsm for RectangleToolFsmState {
|
||||
type ToolData = RectangleToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
let transform = document.root.transform;
|
||||
use RectangleMessage::*;
|
||||
use RectangleToolFsmState::*;
|
||||
if let ToolMessage::Rectangle(event) = event {
|
||||
|
|
@ -72,7 +74,7 @@ impl Fsm for RectangleToolFsmState {
|
|||
data.drag_current = input.mouse.position;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
Dragging
|
||||
}
|
||||
|
|
@ -82,7 +84,7 @@ impl Fsm for RectangleToolFsmState {
|
|||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
|
|
@ -96,13 +98,13 @@ impl Fsm for RectangleToolFsmState {
|
|||
}
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging, transform),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -123,16 +125,17 @@ fn update_state(
|
|||
data: &mut RectangleToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: RectangleToolFsmState,
|
||||
transform: DAffine2,
|
||||
) -> RectangleToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Message {
|
||||
fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
|
|
@ -161,10 +164,7 @@ fn make_operation(data: &RectangleToolData, tool_data: &DocumentToolData) -> Mes
|
|||
Operation::AddRect {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(),
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
.into()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
|||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{message_prelude::*, SvgDocument};
|
||||
use document_core::{layers::style, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Shape {
|
||||
|
|
@ -59,7 +60,8 @@ struct ShapeToolData {
|
|||
impl Fsm for ShapeToolFsmState {
|
||||
type ToolData = ShapeToolData;
|
||||
|
||||
fn transition(self, event: ToolMessage, _document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
fn transition(self, event: ToolMessage, document: &SvgDocument, tool_data: &DocumentToolData, data: &mut Self::ToolData, input: &InputPreprocessor, responses: &mut VecDeque<Message>) -> Self {
|
||||
let transform = document.root.transform;
|
||||
use ShapeMessage::*;
|
||||
use ShapeToolFsmState::*;
|
||||
if let ToolMessage::Shape(event) = event {
|
||||
|
|
@ -76,7 +78,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
(Dragging, MouseMove) => {
|
||||
data.drag_current = input.mouse.position;
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
Dragging
|
||||
}
|
||||
|
|
@ -85,7 +87,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
// TODO - introduce comparison threshold when operating with canvas coordinates (https://github.com/GraphiteEditor/Graphite/issues/100)
|
||||
if data.drag_start != data.drag_current {
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
responses.push_back(Operation::CommitTransaction.into());
|
||||
}
|
||||
|
||||
|
|
@ -99,13 +101,13 @@ impl Fsm for ShapeToolFsmState {
|
|||
|
||||
(Ready, LockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, true, Ready),
|
||||
(Ready, UnlockAspectRatio) => update_state_no_op(&mut data.constrain_to_square, false, Ready),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, LockAspectRatio) => update_state(|data| &mut data.constrain_to_square, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnlockAspectRatio) => update_state(|data| &mut data.constrain_to_square, false, tool_data, data, responses, Dragging, transform),
|
||||
|
||||
(Ready, Center) => update_state_no_op(&mut data.center_around_cursor, true, Ready),
|
||||
(Ready, UnCenter) => update_state_no_op(&mut data.center_around_cursor, false, Ready),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging),
|
||||
(Dragging, Center) => update_state(|data| &mut data.center_around_cursor, true, tool_data, data, responses, Dragging, transform),
|
||||
(Dragging, UnCenter) => update_state(|data| &mut data.center_around_cursor, false, tool_data, data, responses, Dragging, transform),
|
||||
_ => self,
|
||||
}
|
||||
} else {
|
||||
|
|
@ -126,28 +128,30 @@ fn update_state(
|
|||
data: &mut ShapeToolData,
|
||||
responses: &mut VecDeque<Message>,
|
||||
new_state: ShapeToolFsmState,
|
||||
transform: DAffine2,
|
||||
) -> ShapeToolFsmState {
|
||||
*(state(data)) = value;
|
||||
|
||||
responses.push_back(Operation::ClearWorkingFolder.into());
|
||||
responses.push_back(make_operation(data, tool_data));
|
||||
responses.push_back(make_operation(data, tool_data, transform));
|
||||
|
||||
new_state
|
||||
}
|
||||
|
||||
fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Message {
|
||||
fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData, transform: DAffine2) -> Message {
|
||||
let x0 = data.drag_start.x as f64;
|
||||
let y0 = data.drag_start.y as f64;
|
||||
let x1 = data.drag_current.x as f64;
|
||||
let y1 = data.drag_current.y as f64;
|
||||
|
||||
let (x0, y0, x1, y1) = if data.constrain_to_square {
|
||||
// TODO: Use regular polygon's aspect ration for constraining rather than a square.
|
||||
let (x0, y0, x1, y1, equal_sides) = if data.constrain_to_square {
|
||||
let (x_dir, y_dir) = ((x1 - x0).signum(), (y1 - y0).signum());
|
||||
let max_dist = f64::max((x1 - x0).abs(), (y1 - y0).abs());
|
||||
if data.center_around_cursor {
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
(x0 - max_dist * x_dir, y0 - max_dist * y_dir, x0 + max_dist * x_dir, y0 + max_dist * y_dir, true)
|
||||
} else {
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir)
|
||||
(x0, y0, x0 + max_dist * x_dir, y0 + max_dist * y_dir, true)
|
||||
}
|
||||
} else {
|
||||
let (x0, y0) = if data.center_around_cursor {
|
||||
|
|
@ -158,16 +162,14 @@ fn make_operation(data: &ShapeToolData, tool_data: &DocumentToolData) -> Message
|
|||
} else {
|
||||
(x0, y0)
|
||||
};
|
||||
(x0, y0, x1, y1)
|
||||
(x0, y0, x1, y1, false)
|
||||
};
|
||||
|
||||
Operation::AddShape {
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
x0,
|
||||
y0,
|
||||
x1,
|
||||
y1,
|
||||
transform: (transform.inverse() * glam::DAffine2::from_scale_angle_translation(DVec2::new(x1 - x0, y1 - y0), 0., DVec2::new(x0, y0))).to_cols_array(),
|
||||
equal_sides,
|
||||
sides: data.sides,
|
||||
style: style::PathStyle::new(None, Some(style::Fill::new(tool_data.primary_color))),
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue