From 0e1946c4bc4f6ab748e0e26c1402b3dbb2a2c6f6 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Thu, 17 Jun 2021 09:38:11 +0200 Subject: [PATCH 01/13] Initial implementation of copy and paste for layers --- Cargo.lock | 1 + core/document/Cargo.toml | 2 +- core/document/src/document.rs | 7 +++++ core/document/src/layers/circle.rs | 3 +- core/document/src/layers/ellipse.rs | 3 +- core/document/src/layers/folder.rs | 3 +- core/document/src/layers/line.rs | 3 +- core/document/src/layers/mod.rs | 6 ++-- core/document/src/layers/polyline.rs | 3 +- core/document/src/layers/rect.rs | 3 +- core/document/src/layers/shape.rs | 3 +- core/document/src/operation.rs | 9 +++++- core/document/src/shape_points.rs | 3 +- .../src/document/document_message_handler.rs | 31 +++++++++++++++++-- core/editor/src/input/input_mapper.rs | 4 ++- 15 files changed, 69 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6a513ba01..781449e13a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,6 +115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e30b1df631d23875f230ed3ddd1a88c231f269a04b2044eb6ca87e763b5f4c42" dependencies = [ "arrayvec", + "serde", ] [[package]] diff --git a/core/document/Cargo.toml b/core/document/Cargo.toml index 74f5e1ecd5..5202474162 100644 --- a/core/document/Cargo.toml +++ b/core/document/Cargo.toml @@ -10,5 +10,5 @@ license = "Apache-2.0" [dependencies] log = "0.4" -kurbo = "0.8.0" +kurbo = { version = "0.8.0", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 7dc43e48fb..c5eff14675 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -248,6 +248,13 @@ impl Document { let (path, _) = split_path(path.as_slice()).unwrap_or_else(|_| (&[], 0)); Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.to_vec() }]) } + Operation::PasteLayer { path, layer } => { + let folder = self.folder_mut(path)?; + //FIXME: This clone of layer should be avoided somehow + folder.add_layer(layer.clone(), -1).ok_or(DocumentError::IndexOutOfBounds)?; + + Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path: path.clone() }]) + } Operation::DuplicateLayer { path } => { let layer = self.layer(&path)?.clone(); let (folder_path, _) = split_path(path.as_slice()).unwrap_or_else(|_| (&[], 0)); diff --git a/core/document/src/layers/circle.rs b/core/document/src/layers/circle.rs index ad9f066f2a..b51e76a807 100644 --- a/core/document/src/layers/circle.rs +++ b/core/document/src/layers/circle.rs @@ -1,9 +1,10 @@ use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default, Deserialize, Serialize)] pub struct Circle { shape: kurbo::Circle, style: style::PathStyle, diff --git a/core/document/src/layers/ellipse.rs b/core/document/src/layers/ellipse.rs index fe22124f50..339cb1ceed 100644 --- a/core/document/src/layers/ellipse.rs +++ b/core/document/src/layers/ellipse.rs @@ -1,9 +1,10 @@ use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default, Deserialize, Serialize)] pub struct Ellipse { shape: kurbo::Ellipse, style: style::PathStyle, diff --git a/core/document/src/layers/folder.rs b/core/document/src/layers/folder.rs index 71896e2ca4..e806348fef 100644 --- a/core/document/src/layers/folder.rs +++ b/core/document/src/layers/folder.rs @@ -2,9 +2,10 @@ use crate::{DocumentError, LayerId}; use super::{Layer, LayerData, LayerDataTypes}; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Folder { next_assignment_id: LayerId, pub layer_ids: Vec, diff --git a/core/document/src/layers/line.rs b/core/document/src/layers/line.rs index e4b62aeabf..fa1d31c835 100644 --- a/core/document/src/layers/line.rs +++ b/core/document/src/layers/line.rs @@ -1,9 +1,10 @@ use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct Line { shape: kurbo::Line, style: style::PathStyle, diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index e107249da1..a77e0de083 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -21,11 +21,13 @@ pub use shape::Shape; pub mod folder; pub use folder::Folder; +use serde::{Deserialize, Serialize}; + pub trait LayerData { fn render(&mut self, svg: &mut String); } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub enum LayerDataTypes { Folder(Folder), Circle(Circle), @@ -60,7 +62,7 @@ impl LayerDataTypes { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Layer { pub visible: bool, pub name: Option, diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index 38e4364581..949649ada2 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,9 +1,10 @@ use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct PolyLine { points: Vec, style: style::PathStyle, diff --git a/core/document/src/layers/rect.rs b/core/document/src/layers/rect.rs index e38bcd1294..1d850231c6 100644 --- a/core/document/src/layers/rect.rs +++ b/core/document/src/layers/rect.rs @@ -1,9 +1,10 @@ use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct Rect { shape: kurbo::Rect, style: style::PathStyle, diff --git a/core/document/src/layers/shape.rs b/core/document/src/layers/shape.rs index c48ad514f4..18354da3b7 100644 --- a/core/document/src/layers/shape.rs +++ b/core/document/src/layers/shape.rs @@ -3,9 +3,10 @@ use crate::shape_points; use super::style; use super::LayerData; +use serde::{Deserialize, Serialize}; use std::fmt::Write; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct Shape { bounding_rect: kurbo::Rect, shape: shape_points::ShapePoints, diff --git a/core/document/src/operation.rs b/core/document/src/operation.rs index e63ada01ae..3a0d7515cb 100644 --- a/core/document/src/operation.rs +++ b/core/document/src/operation.rs @@ -1,4 +1,7 @@ -use crate::{layers::style, LayerId}; +use crate::{ + layers::{style, Layer}, + LayerId, +}; use serde::{Deserialize, Serialize}; @@ -63,6 +66,10 @@ pub enum Operation { DuplicateLayer { path: Vec, }, + PasteLayer { + layer: Layer, + path: Vec, + }, AddFolder { path: Vec, }, diff --git a/core/document/src/shape_points.rs b/core/document/src/shape_points.rs index 4b351bd568..879777f90a 100644 --- a/core/document/src/shape_points.rs +++ b/core/document/src/shape_points.rs @@ -1,8 +1,9 @@ use std::{fmt, ops::Add}; use kurbo::{PathEl, Point, Vec2}; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] pub struct ShapePoints { center: kurbo::Point, extent: kurbo::Vec2, diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 269a1f9e4f..0fde46b3b3 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -1,5 +1,7 @@ use crate::message_prelude::*; +use document_core::layers::Layer; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; +use log::{debug, warn}; use crate::document::Document; use std::collections::VecDeque; @@ -12,6 +14,8 @@ pub enum DocumentMessage { DeleteLayer(Vec), DeleteSelectedLayers, DuplicateSelectedLayers, + CopySelectedLayers, + PasteLayers, AddFolder(Vec), RenameLayer(Vec, String), ToggleLayerVisibility(Vec), @@ -40,6 +44,7 @@ impl From for Message { pub struct DocumentMessageHandler { documents: Vec, active_document: usize, + copy_buffer: Vec, } impl DocumentMessageHandler { @@ -76,6 +81,7 @@ impl Default for DocumentMessageHandler { Self { documents: vec![Document::default()], active_document: 0, + copy_buffer: vec![], } } } @@ -166,6 +172,27 @@ impl MessageHandler for DocumentMessageHandler { responses.push_back(DocumentOperation::DuplicateLayer { path }.into()) } } + CopySelectedLayers => { + let paths: Vec> = self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())).collect(); + //FIXME: The `paths` and thus the `copy_buffer` are not in the correct order. + self.copy_buffer.clear(); + for path in paths { + match self.active_document().document.layer(&path).map(|t| t.clone()) { + Ok(layer) => { + self.copy_buffer.push(layer); + } + Err(e) => warn!("Could not access selected layer {:?}: {:?}", path, e), + } + } + debug!("CopySelectedLayers copied {} layers: {:?}", self.copy_buffer.len(), self.copy_buffer); + } + PasteLayers => { + for layer in self.copy_buffer.iter() { + //TODO: Should be the path to the current folder instead of root + responses.push_back(DocumentOperation::PasteLayer { layer: layer.clone(), path: vec![] }.into()) + } + } + SelectLayers(paths) => { self.clear_selection(); for path in paths { @@ -214,9 +241,9 @@ impl MessageHandler for DocumentMessageHandler { } fn actions(&self) -> ActionList { if self.active_document().layer_data.values().any(|data| data.selected) { - actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, NextDocument, PrevDocument) + actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, CopySelectedLayers, PasteLayers, RenderDocument, ExportDocument, NewDocument, NextDocument, PrevDocument) } else { - actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, NextDocument, PrevDocument) + actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, NextDocument, PrevDocument, PasteLayers) } } } diff --git a/core/editor/src/input/input_mapper.rs b/core/editor/src/input/input_mapper.rs index 5c2144cdf1..4dc0b43635 100644 --- a/core/editor/src/input/input_mapper.rs +++ b/core/editor/src/input/input_mapper.rs @@ -96,6 +96,7 @@ macro_rules! mapping { impl Default for Mapping { fn default() -> Self { let (up, down, pointer_move) = mapping![ + entry! {action=DocumentMessage::PasteLayers, key_down=KeyV, modifiers=[KeyControl]}, // Rectangle entry! {action=RectangleMessage::Center, key_down=KeyAlt}, entry! {action=RectangleMessage::UnCenter, key_up=KeyAlt}, @@ -163,11 +164,12 @@ impl Default for Mapping { entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]}, entry! {action=DocumentMessage::NewDocument, key_down=KeyN, modifiers=[KeyShift]}, entry! {action=DocumentMessage::NextDocument, key_down=KeyTab, modifiers=[KeyShift]}, + entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]}, + entry! {action=DocumentMessage::CopySelectedLayers, key_down=KeyC, modifiers=[KeyControl]}, // Global Actions entry! {action=GlobalMessage::LogInfo, key_down=Key1}, entry! {action=GlobalMessage::LogDebug, key_down=Key2}, entry! {action=GlobalMessage::LogTrace, key_down=Key3}, - entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]}, ]; Self { up, down, pointer_move } } From 65faac5c2be6df79a37c28343203d8e90cda291e Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sat, 19 Jun 2021 10:46:36 +0200 Subject: [PATCH 02/13] Sort layers on copy and add tests --- Cargo.lock | 118 ++++++++++ core/document/src/document.rs | 23 +- core/editor/Cargo.toml | 3 + core/editor/src/communication/dispatcher.rs | 220 ++++++++++++++++++ .../src/document/document_message_handler.rs | 27 ++- 5 files changed, 388 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 781449e13a..11c438f5f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,32 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "arrayvec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "bitflags" version = "1.2.1" @@ -42,6 +62,19 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "graphite-cli" version = "0.1.0" @@ -60,6 +93,7 @@ name = "graphite-editor-core" version = "0.1.0" dependencies = [ "bitflags", + "env_logger", "graphite-document-core", "graphite-proc-macros", "log", @@ -93,6 +127,21 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "itoa" version = "0.4.7" @@ -124,6 +173,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "libc" +version = "0.2.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" + [[package]] name = "log" version = "0.4.14" @@ -133,6 +188,12 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "memchr" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" + [[package]] name = "proc-macro2" version = "1.0.26" @@ -151,6 +212,23 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "ryu" version = "1.0.5" @@ -205,6 +283,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.24" @@ -332,3 +419,34 @@ dependencies = [ "js-sys", "wasm-bindgen", ] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/core/document/src/document.rs b/core/document/src/document.rs index c5eff14675..0ee9690436 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -5,7 +5,7 @@ use crate::{ #[derive(Debug, Clone, PartialEq)] pub struct Document { - pub root: layers::Folder, + pub root: Folder, pub work: Folder, pub work_mount_path: Vec, pub work_operations: Vec, @@ -128,6 +128,27 @@ impl Document { self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound) } + pub fn indices_for_path(&self, mut path: &[LayerId]) -> Result, DocumentError> { + let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { + path = &path[self.work_mount_path.len()..]; + &self.work + } else { + &self.root + }; + let mut indices = vec![]; + let (path, layer_id) = split_path(path)?; + + for id in path { + let pos = root.layer_ids.iter().position(|x| *x == *id).ok_or(DocumentError::LayerNotFound)?; + indices.push(pos); + root = root.folder(*id).ok_or(DocumentError::LayerNotFound)?; + } + + indices.push(root.layer_ids.iter().position(|x| *x == layer_id).ok_or(DocumentError::LayerNotFound)?); + + Ok(indices) + } + /// Returns a mutable reference to the layer struct at the specified `path`. /// If you manually edit the layer you have to set the cache_dirty flag yourself. pub fn layer_mut(&mut self, path: &[LayerId]) -> Result<&mut Layer, DocumentError> { diff --git a/core/editor/Cargo.toml b/core/editor/Cargo.toml index fe90d2043d..c166a649e6 100644 --- a/core/editor/Cargo.toml +++ b/core/editor/Cargo.toml @@ -18,3 +18,6 @@ graphite-proc-macros = {path = "../proc-macro"} [dependencies.document-core] path = "../document" package = "graphite-document-core" + +[dev-dependencies] +env_logger = "0.8.4" diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index 644bf93891..e46f9716f6 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -76,3 +76,223 @@ impl Dispatcher { } } } + +#[cfg(test)] +mod test { + use crate::{ + input::{ + mouse::{MouseKeys, MouseState, ViewportPosition}, + InputPreprocessorMessage, + }, + message_prelude::{DocumentMessage, Message, ToolMessage}, + tool::ToolType, + Editor, + }; + use document_core::color::Color; + use log::info; + + /// A set of utility functions to make the writing of editor test more declarative + trait EditorTestUtils { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + + /// Select given tool and drag it from (x1, y1) to (x2, y2) + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); + fn move_mouse(&mut self, x: u32, y: u32); + fn mousedown(&mut self, state: MouseState); + fn mouseup(&mut self, state: MouseState); + fn left_mousedown(&mut self, x: u32, y: u32); + fn left_mouseup(&mut self, x: u32, y: u32); + fn input(&mut self, message: InputPreprocessorMessage); + fn select_tool(&mut self, typ: ToolType); + fn select_primary_color(&mut self, color: Color); + } + + impl EditorTestUtils for Editor { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Rectangle, x1, y1, x2, y2); + } + + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Shape, x1, y1, x2, y2); + } + + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Ellipse, x1, y1, x2, y2); + } + + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32) { + self.select_tool(typ); + self.move_mouse(x1, y1); + self.left_mousedown(x1, y1); + self.move_mouse(x2, y2); + self.left_mouseup(x2, y2); + } + + fn move_mouse(&mut self, x: u32, y: u32) { + self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y })); + } + + fn mousedown(&mut self, state: MouseState) { + self.input(InputPreprocessorMessage::MouseDown(state)); + } + + fn mouseup(&mut self, state: MouseState) { + self.input(InputPreprocessorMessage::MouseUp(state)); + } + + fn left_mousedown(&mut self, x: u32, y: u32) { + self.mousedown(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::LEFT, + }) + } + + fn left_mouseup(&mut self, x: u32, y: u32) { + self.mouseup(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::empty(), + }) + } + + fn input(&mut self, message: InputPreprocessorMessage) { + self.handle_message(Message::InputPreprocessor(message)).unwrap(); + } + + fn select_tool(&mut self, typ: ToolType) { + self.handle_message(Message::Tool(ToolMessage::SelectTool(typ))).unwrap(); + } + + fn select_primary_color(&mut self, color: Color) { + self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor(color))).unwrap(); + } + } + + /// Create an editor instance with three layers + /// 1. A red rectangle + /// 2. A blue shape + /// 3. A green ellipse + fn create_editor_with_three_layers() -> Editor { + let mut editor = Editor::new(Box::new(|e| { + info!("Got frontend message: {:?}", e); + })); + + editor.select_primary_color(Color::RED); + editor.draw_rect(100, 200, 300, 400); + editor.select_primary_color(Color::BLUE); + editor.draw_shape(10, 1200, 1300, 400); + editor.select_primary_color(Color::GREEN); + editor.draw_ellipse(104, 1200, 1300, 400); + + editor + } + + #[test] + /// - create rect, shape and ellipse + /// - copy + /// - paste + /// - assert that ellipse was copied + fn copy_paste_single_layer() { + env_logger::init(); + let mut editor = create_editor_with_three_layers(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.layers(); + let layers_after_copy = document_after_copy.root.layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 4); + + // Existing layers are unaffected + for i in 0..=2 { + assert_eq!(layers_before_copy[i], layers_after_copy[i]); + } + + // The ellipse was copied + assert_eq!(layers_before_copy[2], layers_after_copy[3]); + } + + #[test] + /// - create rect, shape and ellipse + /// - select shape + /// - copy + /// - paste + /// - assert that shape was copied + fn copy_paste_single_layer_from_middle() { + env_logger::init(); + let mut editor = create_editor_with_three_layers(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + let shape_id = document_before_copy.root.layer_ids[1]; + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![shape_id]]))).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.layers(); + let layers_after_copy = document_after_copy.root.layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 4); + + // Existing layers are unaffected + for i in 0..=2 { + assert_eq!(layers_before_copy[i], layers_after_copy[i]); + } + + // The shape was copied + assert_eq!(layers_before_copy[1], layers_after_copy[3]); + } + + #[test] + /// - create rect, shape and ellipse + /// - select ellipse and rect + /// - copy + /// - delete + /// - create another rect + /// - paste + /// - paste + fn copy_paste_deleted_layer() { + env_logger::init(); + let mut editor = create_editor_with_three_layers(); + + const ELLIPSE_INDEX: usize = 2; + const SHAPE_INDEX: usize = 1; + const RECT_INDEX: usize = 0; + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + let rect_id = document_before_copy.root.layer_ids[RECT_INDEX]; + let ellipse_id = document_before_copy.root.layer_ids[ELLIPSE_INDEX]; + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![rect_id], vec![ellipse_id]]))).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::DeleteSelectedLayers)).unwrap(); + editor.draw_rect(0, 800, 12, 200); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.layers(); + let layers_after_copy = document_after_copy.root.layers(); + + assert_eq!(layers_before_copy.len(), 3); + assert_eq!(layers_after_copy.len(), 5); + + let rect_before_copy = &layers_before_copy[RECT_INDEX]; + let ellipse_before_copy = &layers_before_copy[ELLIPSE_INDEX]; + + assert_eq!(layers_after_copy[0], layers_before_copy[SHAPE_INDEX]); + assert_eq!(&layers_after_copy[1], rect_before_copy); + assert_eq!(&layers_after_copy[2], ellipse_before_copy); + assert_eq!(&layers_after_copy[3], rect_before_copy); + assert_eq!(&layers_after_copy[4], ellipse_before_copy); + } +} diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 0fde46b3b3..7835ecac00 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -74,6 +74,30 @@ impl DocumentMessageHandler { // TODO: Add deduplication (!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten() } + + fn selected_layers_sorted(&self) -> Vec> { + let mut x: Vec<(Vec, Vec)> = self + .active_document() + .layer_data + .iter() + .filter_map(|(path, data)| data.selected.then(|| path.clone())) + .map(|e| { + let i = self.active_document().document.indices_for_path(&e); + debug!("selected_layers_sorted a layer {:?} became {:?}", e, i); + (e, i) + }) + .filter_map(|(e, indices)| match indices { + Err(e) => { + warn!("selected_layers_sorted {:?}", e); + None + } + Ok(v) => Some((e, v)), + }) + .collect(); + + x.sort_by_key(|(e, b)| b.clone()); + return x.into_iter().map(|(e, i)| e).collect(); + } } impl Default for DocumentMessageHandler { @@ -173,8 +197,7 @@ impl MessageHandler for DocumentMessageHandler { } } CopySelectedLayers => { - let paths: Vec> = self.active_document().layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())).collect(); - //FIXME: The `paths` and thus the `copy_buffer` are not in the correct order. + let paths: Vec> = self.selected_layers_sorted(); self.copy_buffer.clear(); for path in paths { match self.active_document().document.layer(&path).map(|t| t.clone()) { From ba7ee0d717008edcde94444c9be798df89d472f3 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sat, 19 Jun 2021 11:10:07 +0200 Subject: [PATCH 03/13] Fix logger init for test --- core/editor/src/communication/dispatcher.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index e46f9716f6..d9cd93ed17 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -91,6 +91,10 @@ mod test { use document_core::color::Color; use log::info; + fn init_logger() { + let _ = env_logger::builder().is_test(true).try_init(); + } + /// A set of utility functions to make the writing of editor test more declarative trait EditorTestUtils { fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); @@ -194,7 +198,7 @@ mod test { /// - paste /// - assert that ellipse was copied fn copy_paste_single_layer() { - env_logger::init(); + init_logger(); let mut editor = create_editor_with_three_layers(); let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); @@ -224,7 +228,7 @@ mod test { /// - paste /// - assert that shape was copied fn copy_paste_single_layer_from_middle() { - env_logger::init(); + init_logger(); let mut editor = create_editor_with_three_layers(); let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); @@ -260,7 +264,7 @@ mod test { /// - paste /// - paste fn copy_paste_deleted_layer() { - env_logger::init(); + init_logger(); let mut editor = create_editor_with_three_layers(); const ELLIPSE_INDEX: usize = 2; From 70c2ea7660e43fd8599d9038756a27095be8ee7a Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sat, 19 Jun 2021 11:18:04 +0200 Subject: [PATCH 04/13] Fix `copy_paste_deleted_layers` test --- core/editor/src/communication/dispatcher.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index d9cd93ed17..3a40ca2a13 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -263,7 +263,7 @@ mod test { /// - create another rect /// - paste /// - paste - fn copy_paste_deleted_layer() { + fn copy_paste_deleted_layers() { init_logger(); let mut editor = create_editor_with_three_layers(); @@ -288,15 +288,15 @@ mod test { let layers_after_copy = document_after_copy.root.layers(); assert_eq!(layers_before_copy.len(), 3); - assert_eq!(layers_after_copy.len(), 5); + assert_eq!(layers_after_copy.len(), 6); let rect_before_copy = &layers_before_copy[RECT_INDEX]; let ellipse_before_copy = &layers_before_copy[ELLIPSE_INDEX]; assert_eq!(layers_after_copy[0], layers_before_copy[SHAPE_INDEX]); - assert_eq!(&layers_after_copy[1], rect_before_copy); - assert_eq!(&layers_after_copy[2], ellipse_before_copy); - assert_eq!(&layers_after_copy[3], rect_before_copy); - assert_eq!(&layers_after_copy[4], ellipse_before_copy); + assert_eq!(&layers_after_copy[2], rect_before_copy); + assert_eq!(&layers_after_copy[3], ellipse_before_copy); + assert_eq!(&layers_after_copy[4], rect_before_copy); + assert_eq!(&layers_after_copy[5], ellipse_before_copy); } } From b543f0c513508b1bae9375afb8397402bb42e839 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sun, 27 Jun 2021 13:09:39 +0200 Subject: [PATCH 05/13] Readd erroneously removed svg --- client/web/assets/16px-solid/viewport-guide-mode.svg | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 client/web/assets/16px-solid/viewport-guide-mode.svg diff --git a/client/web/assets/16px-solid/viewport-guide-mode.svg b/client/web/assets/16px-solid/viewport-guide-mode.svg new file mode 100644 index 0000000000..d78860e98d --- /dev/null +++ b/client/web/assets/16px-solid/viewport-guide-mode.svg @@ -0,0 +1,6 @@ + + + + + + From 5b43d49277908784ba6cd5aa0f2f3ebf488ab215 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sun, 27 Jun 2021 14:24:20 +0200 Subject: [PATCH 06/13] Make Layer serializable and cleanup --- core/document/src/document.rs | 3 +- core/document/src/layers/mod.rs | 11 +++++- core/document/src/layers/polyline.rs | 4 +-- core/editor/src/communication/dispatcher.rs | 18 +++++----- .../src/document/document_message_handler.rs | 36 +++++++++---------- 5 files changed, 39 insertions(+), 33 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 23229dfe93..7d255ea411 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -139,7 +139,8 @@ impl Document { &self.work } else { &self.root - }.as_folder()?; + } + .as_folder()?; let mut indices = vec![]; let (path, layer_id) = split_path(path)?; diff --git a/core/document/src/layers/mod.rs b/core/document/src/layers/mod.rs index 2bd36dae4c..48a8ebbdcd 100644 --- a/core/document/src/layers/mod.rs +++ b/core/document/src/layers/mod.rs @@ -4,6 +4,7 @@ pub mod ellipse; pub use ellipse::Ellipse; pub mod line; +use glam::{DMat2, DVec2}; use kurbo::BezPath; pub use line::Line; @@ -17,9 +18,9 @@ pub mod shape; pub use shape::Shape; pub mod folder; +use crate::DocumentError; pub use folder::Folder; use serde::{Deserialize, Serialize}; -use crate::DocumentError; pub trait LayerData { fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle); @@ -77,11 +78,19 @@ impl LayerDataTypes { } } +#[derive(Serialize, Deserialize)] +#[serde(remote = "glam::DAffine2")] +struct DAffine2Ref { + pub matrix2: DMat2, + pub translation: DVec2, +} + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct Layer { pub visible: bool, pub name: Option, pub data: LayerDataTypes, + #[serde(with = "DAffine2Ref")] pub transform: glam::DAffine2, pub style: style::PathStyle, pub cache: String, diff --git a/core/document/src/layers/polyline.rs b/core/document/src/layers/polyline.rs index a6152a157f..c2558fae13 100644 --- a/core/document/src/layers/polyline.rs +++ b/core/document/src/layers/polyline.rs @@ -1,7 +1,7 @@ -use std::fmt::Write; use serde::{Deserialize, Serialize}; +use std::fmt::Write; -use super::{LayerData, style}; +use super::{style, LayerData}; #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] pub struct PolyLine { diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index d8fb76c1db..9986017cdb 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -206,8 +206,8 @@ mod test { editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); - let layers_before_copy = document_before_copy.root.layers(); - let layers_after_copy = document_after_copy.root.layers(); + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); assert_eq!(layers_before_copy.len(), 3); assert_eq!(layers_after_copy.len(), 4); @@ -232,7 +232,7 @@ mod test { let mut editor = create_editor_with_three_layers(); let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); - let shape_id = document_before_copy.root.layer_ids[1]; + let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1]; editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![shape_id]]))).unwrap(); editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); @@ -240,8 +240,8 @@ mod test { let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); - let layers_before_copy = document_before_copy.root.layers(); - let layers_after_copy = document_after_copy.root.layers(); + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); assert_eq!(layers_before_copy.len(), 3); assert_eq!(layers_after_copy.len(), 4); @@ -272,8 +272,8 @@ mod test { const RECT_INDEX: usize = 0; let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); - let rect_id = document_before_copy.root.layer_ids[RECT_INDEX]; - let ellipse_id = document_before_copy.root.layer_ids[ELLIPSE_INDEX]; + let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX]; + let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX]; editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![rect_id], vec![ellipse_id]]))).unwrap(); editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); @@ -284,8 +284,8 @@ mod test { let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); - let layers_before_copy = document_before_copy.root.layers(); - let layers_after_copy = document_after_copy.root.layers(); + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); assert_eq!(layers_before_copy.len(), 3); assert_eq!(layers_after_copy.len(), 6); diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 71f60265b6..1b14904fdb 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -1,13 +1,9 @@ -use crate::{ - input::{mouse::ViewportPosition, InputPreprocessor}, - message_prelude::*, -}; +use crate::input::{mouse::ViewportPosition, InputPreprocessor}; +use crate::message_prelude::*; +use document_core::layers::Layer; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; use glam::{DAffine2, DVec2}; -use log::info; use log::{debug, warn}; -use crate::message_prelude::*; -use document_core::layers::Layer; use crate::document::Document; use std::collections::VecDeque; @@ -89,27 +85,27 @@ impl DocumentMessageHandler { } fn selected_layers_sorted(&self) -> Vec> { - let mut x: Vec<(Vec, Vec)> = self + let mut layers_with_indices: Vec<(Vec, Vec)> = self .active_document() .layer_data .iter() .filter_map(|(path, data)| data.selected.then(|| path.clone())) - .map(|e| { - let i = self.active_document().document.indices_for_path(&e); - debug!("selected_layers_sorted a layer {:?} became {:?}", e, i); - (e, i) - }) - .filter_map(|(e, indices)| match indices { - Err(e) => { - warn!("selected_layers_sorted {:?}", e); - None + .filter_map(|layer_id| { + let indices = self.active_document().document.indices_for_path(&layer_id); + debug!("selected_layers_sorted: layer {:?} became {:?}", layer_id, indices); + + match indices { + Err(e) => { + warn!("selected_layers_sorted: Could not get indices for the layer {:?}: {:?}", layer_id, e); + None + } + Ok(v) => Some((layer_id, v)), } - Ok(v) => Some((e, v)), }) .collect(); - x.sort_by_key(|(e, b)| b.clone()); - return x.into_iter().map(|(e, i)| e).collect(); + layers_with_indices.sort_by_key(|(_, b)| b.clone()); + return layers_with_indices.into_iter().map(|(e, _)| e).collect(); } } From 5bd769de7ad2e88e6386710a78a37f2c67de8ce0 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sun, 27 Jun 2021 22:07:17 +0200 Subject: [PATCH 07/13] Add test for copy and pasting folders --- core/document/src/layers/circle.rs | 0 core/document/src/shape_points.rs | 0 core/editor/src/communication/dispatcher.rs | 168 ++++++++++---------- core/editor/src/misc/mod.rs | 1 + core/editor/src/misc/test_utils.rs | 86 ++++++++++ 5 files changed, 171 insertions(+), 84 deletions(-) delete mode 100644 core/document/src/layers/circle.rs delete mode 100644 core/document/src/shape_points.rs create mode 100644 core/editor/src/misc/test_utils.rs diff --git a/core/document/src/layers/circle.rs b/core/document/src/layers/circle.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core/document/src/shape_points.rs b/core/document/src/shape_points.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index 9986017cdb..d608aed0e7 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -80,98 +80,18 @@ impl Dispatcher { #[cfg(test)] mod test { use crate::{ - input::{ - mouse::{MouseKeys, MouseState, ViewportPosition}, - InputPreprocessorMessage, - }, - message_prelude::{DocumentMessage, Message, ToolMessage}, - tool::ToolType, + message_prelude::{DocumentMessage, Message}, + misc::test_utils::EditorTestUtils, Editor, }; - use document_core::color::Color; - use log::info; + use document_core::{color::Color, Operation}; + use log::{debug, info}; fn init_logger() { let _ = env_logger::builder().is_test(true).try_init(); } /// A set of utility functions to make the writing of editor test more declarative - trait EditorTestUtils { - fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); - fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); - fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); - - /// Select given tool and drag it from (x1, y1) to (x2, y2) - fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); - fn move_mouse(&mut self, x: u32, y: u32); - fn mousedown(&mut self, state: MouseState); - fn mouseup(&mut self, state: MouseState); - fn left_mousedown(&mut self, x: u32, y: u32); - fn left_mouseup(&mut self, x: u32, y: u32); - fn input(&mut self, message: InputPreprocessorMessage); - fn select_tool(&mut self, typ: ToolType); - fn select_primary_color(&mut self, color: Color); - } - - impl EditorTestUtils for Editor { - fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { - self.drag_tool(ToolType::Rectangle, x1, y1, x2, y2); - } - - fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { - self.drag_tool(ToolType::Shape, x1, y1, x2, y2); - } - - fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { - self.drag_tool(ToolType::Ellipse, x1, y1, x2, y2); - } - - fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32) { - self.select_tool(typ); - self.move_mouse(x1, y1); - self.left_mousedown(x1, y1); - self.move_mouse(x2, y2); - self.left_mouseup(x2, y2); - } - - fn move_mouse(&mut self, x: u32, y: u32) { - self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y })); - } - - fn mousedown(&mut self, state: MouseState) { - self.input(InputPreprocessorMessage::MouseDown(state)); - } - - fn mouseup(&mut self, state: MouseState) { - self.input(InputPreprocessorMessage::MouseUp(state)); - } - - fn left_mousedown(&mut self, x: u32, y: u32) { - self.mousedown(MouseState { - position: ViewportPosition { x, y }, - mouse_keys: MouseKeys::LEFT, - }) - } - - fn left_mouseup(&mut self, x: u32, y: u32) { - self.mouseup(MouseState { - position: ViewportPosition { x, y }, - mouse_keys: MouseKeys::empty(), - }) - } - - fn input(&mut self, message: InputPreprocessorMessage) { - self.handle_message(Message::InputPreprocessor(message)).unwrap(); - } - - fn select_tool(&mut self, typ: ToolType) { - self.handle_message(Message::Tool(ToolMessage::SelectTool(typ))).unwrap(); - } - - fn select_primary_color(&mut self, color: Color) { - self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor(color))).unwrap(); - } - } /// Create an editor instance with three layers /// 1. A red rectangle @@ -255,6 +175,86 @@ mod test { assert_eq!(layers_before_copy[1], layers_after_copy[3]); } + #[test] + fn copy_paste_folder() { + init_logger(); + let mut editor = create_editor_with_three_layers(); + + const FOLDER_INDEX: usize = 3; + const ELLIPSE_INDEX: usize = 2; + const SHAPE_INDEX: usize = 1; + const RECT_INDEX: usize = 0; + + const LINE_INDEX: usize = 0; + const PEN_INDEX: usize = 1; + + editor.handle_message(Message::Document(DocumentMessage::AddFolder(vec![]))).unwrap(); + + let document_before_added_shapes = editor.dispatcher.document_message_handler.active_document().document.clone(); + let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX]; + + editor + .handle_message(Message::Document(DocumentMessage::DispatchOperation(Operation::AddLine { + path: vec![folder_id], + insert_index: 0, + transform: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + style: Default::default(), + }))) + .unwrap(); + + editor + .handle_message(Message::Document(DocumentMessage::DispatchOperation(Operation::AddPen { + path: vec![folder_id], + insert_index: 0, + transform: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + style: Default::default(), + points: vec![(10.0, 20.0), (30.0, 40.0)], + }))) + .unwrap(); + + editor.handle_message(Message::Document(DocumentMessage::SelectLayers(vec![vec![folder_id]]))).unwrap(); + + let document_before_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + editor.handle_message(Message::Document(DocumentMessage::CopySelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::DeleteSelectedLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + editor.handle_message(Message::Document(DocumentMessage::PasteLayers)).unwrap(); + + let document_after_copy = editor.dispatcher.document_message_handler.active_document().document.clone(); + + let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers(); + let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers(); + + assert_eq!(layers_before_copy.len(), 4); + assert_eq!(layers_after_copy.len(), 5); + + let rect_before_copy = &layers_before_copy[RECT_INDEX]; + let ellipse_before_copy = &layers_before_copy[ELLIPSE_INDEX]; + let shape_before_copy = &layers_before_copy[SHAPE_INDEX]; + let folder_before_copy = &layers_before_copy[FOLDER_INDEX]; + let line_before_copy = folder_before_copy.as_folder().unwrap().layers()[LINE_INDEX].clone(); + let pen_before_copy = folder_before_copy.as_folder().unwrap().layers()[PEN_INDEX].clone(); + + assert_eq!(&layers_after_copy[0], rect_before_copy); + assert_eq!(&layers_after_copy[1], shape_before_copy); + assert_eq!(&layers_after_copy[2], ellipse_before_copy); + assert_eq!(&layers_after_copy[3], folder_before_copy); + assert_eq!(&layers_after_copy[4], folder_before_copy); + + // Check the layers inside the two folders + let first_folder_layers_after_copy = layers_after_copy[3].as_folder().unwrap().layers(); + let second_folder_layers_after_copy = layers_after_copy[4].as_folder().unwrap().layers(); + + assert_eq!(first_folder_layers_after_copy[0], line_before_copy); + assert_eq!(first_folder_layers_after_copy[1], pen_before_copy); + + assert_eq!(second_folder_layers_after_copy[0], line_before_copy); + assert_eq!(second_folder_layers_after_copy[1], pen_before_copy); + + debug!("end doc{:#?}", editor.dispatcher.document_message_handler.active_document().document.clone()) + } + #[test] /// - create rect, shape and ellipse /// - select ellipse and rect diff --git a/core/editor/src/misc/mod.rs b/core/editor/src/misc/mod.rs index ad0b8560a0..963324ed36 100644 --- a/core/editor/src/misc/mod.rs +++ b/core/editor/src/misc/mod.rs @@ -2,6 +2,7 @@ pub mod macros; pub mod derivable_custom_traits; mod error; +pub mod test_utils; pub use error::EditorError; pub use macros::*; diff --git a/core/editor/src/misc/test_utils.rs b/core/editor/src/misc/test_utils.rs new file mode 100644 index 0000000000..7ba4e072ce --- /dev/null +++ b/core/editor/src/misc/test_utils.rs @@ -0,0 +1,86 @@ +use crate::{ + input::{ + mouse::{MouseKeys, MouseState, ViewportPosition}, + InputPreprocessorMessage, + }, + message_prelude::{Message, ToolMessage}, + tool::ToolType, + Editor, +}; +use document_core::color::Color; +pub trait EditorTestUtils { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); + + /// Select given tool and drag it from (x1, y1) to (x2, y2) + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); + fn move_mouse(&mut self, x: u32, y: u32); + fn mousedown(&mut self, state: MouseState); + fn mouseup(&mut self, state: MouseState); + fn left_mousedown(&mut self, x: u32, y: u32); + fn left_mouseup(&mut self, x: u32, y: u32); + fn input(&mut self, message: InputPreprocessorMessage); + fn select_tool(&mut self, typ: ToolType); + fn select_primary_color(&mut self, color: Color); +} + +impl EditorTestUtils for Editor { + fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Rectangle, x1, y1, x2, y2); + } + + fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Shape, x1, y1, x2, y2); + } + + fn draw_ellipse(&mut self, x1: u32, y1: u32, x2: u32, y2: u32) { + self.drag_tool(ToolType::Ellipse, x1, y1, x2, y2); + } + + fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32) { + self.select_tool(typ); + self.move_mouse(x1, y1); + self.left_mousedown(x1, y1); + self.move_mouse(x2, y2); + self.left_mouseup(x2, y2); + } + + fn move_mouse(&mut self, x: u32, y: u32) { + self.input(InputPreprocessorMessage::MouseMove(ViewportPosition { x, y })); + } + + fn mousedown(&mut self, state: MouseState) { + self.input(InputPreprocessorMessage::MouseDown(state)); + } + + fn mouseup(&mut self, state: MouseState) { + self.input(InputPreprocessorMessage::MouseUp(state)); + } + + fn left_mousedown(&mut self, x: u32, y: u32) { + self.mousedown(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::LEFT, + }) + } + + fn left_mouseup(&mut self, x: u32, y: u32) { + self.mouseup(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::empty(), + }) + } + + fn input(&mut self, message: InputPreprocessorMessage) { + self.handle_message(Message::InputPreprocessor(message)).unwrap(); + } + + fn select_tool(&mut self, typ: ToolType) { + self.handle_message(Message::Tool(ToolMessage::SelectTool(typ))).unwrap(); + } + + fn select_primary_color(&mut self, color: Color) { + self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor(color))).unwrap(); + } +} From 494095378d54abbaa9508cb7b3975055a4d526ff Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sun, 27 Jun 2021 22:55:12 +0200 Subject: [PATCH 08/13] Cleanup --- core/editor/src/communication/dispatcher.rs | 8 +++----- core/editor/src/misc/test_utils.rs | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index d608aed0e7..e19afe98f0 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -85,14 +85,12 @@ mod test { Editor, }; use document_core::{color::Color, Operation}; - use log::{debug, info}; + use log::info; fn init_logger() { let _ = env_logger::builder().is_test(true).try_init(); } - /// A set of utility functions to make the writing of editor test more declarative - /// Create an editor instance with three layers /// 1. A red rectangle /// 2. A blue shape @@ -193,6 +191,8 @@ mod test { let document_before_added_shapes = editor.dispatcher.document_message_handler.active_document().document.clone(); let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX]; + // TODO: This adding of a Line and Pen should be rewritten using the corresponding functions in EditorTestUtils. + // This has not been done yet as the line and pen tool are not yet able to add layers to the currently selected folder editor .handle_message(Message::Document(DocumentMessage::DispatchOperation(Operation::AddLine { path: vec![folder_id], @@ -251,8 +251,6 @@ mod test { assert_eq!(second_folder_layers_after_copy[0], line_before_copy); assert_eq!(second_folder_layers_after_copy[1], pen_before_copy); - - debug!("end doc{:#?}", editor.dispatcher.document_message_handler.active_document().document.clone()) } #[test] diff --git a/core/editor/src/misc/test_utils.rs b/core/editor/src/misc/test_utils.rs index 7ba4e072ce..42c95844b9 100644 --- a/core/editor/src/misc/test_utils.rs +++ b/core/editor/src/misc/test_utils.rs @@ -8,6 +8,8 @@ use crate::{ Editor, }; use document_core::color::Color; + +/// A set of utility functions to make the writing of editor test more declarative pub trait EditorTestUtils { fn draw_rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); fn draw_shape(&mut self, x1: u32, y1: u32, x2: u32, y2: u32); From 133828d5774edac9e2e51be5e6df185435f66964 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Sun, 27 Jun 2021 23:46:58 +0200 Subject: [PATCH 09/13] Rename left_mouseup --- core/editor/src/misc/test_utils.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/core/editor/src/misc/test_utils.rs b/core/editor/src/misc/test_utils.rs index 42c95844b9..5563e4140c 100644 --- a/core/editor/src/misc/test_utils.rs +++ b/core/editor/src/misc/test_utils.rs @@ -19,9 +19,8 @@ pub trait EditorTestUtils { fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); fn move_mouse(&mut self, x: u32, y: u32); fn mousedown(&mut self, state: MouseState); - fn mouseup(&mut self, state: MouseState); + fn mouseup(&mut self, x: u32, y: u32); fn left_mousedown(&mut self, x: u32, y: u32); - fn left_mouseup(&mut self, x: u32, y: u32); fn input(&mut self, message: InputPreprocessorMessage); fn select_tool(&mut self, typ: ToolType); fn select_primary_color(&mut self, color: Color); @@ -45,7 +44,7 @@ impl EditorTestUtils for Editor { self.move_mouse(x1, y1); self.left_mousedown(x1, y1); self.move_mouse(x2, y2); - self.left_mouseup(x2, y2); + self.mouseup(x2, y2); } fn move_mouse(&mut self, x: u32, y: u32) { @@ -56,8 +55,12 @@ impl EditorTestUtils for Editor { self.input(InputPreprocessorMessage::MouseDown(state)); } - fn mouseup(&mut self, state: MouseState) { - self.input(InputPreprocessorMessage::MouseUp(state)); + fn mouseup(&mut self, x: u32, y: u32) { + self.handle_message(InputPreprocessorMessage::MouseUp(MouseState { + position: ViewportPosition { x, y }, + mouse_keys: MouseKeys::empty(), + })) + .unwrap() } fn left_mousedown(&mut self, x: u32, y: u32) { @@ -67,13 +70,6 @@ impl EditorTestUtils for Editor { }) } - fn left_mouseup(&mut self, x: u32, y: u32) { - self.mouseup(MouseState { - position: ViewportPosition { x, y }, - mouse_keys: MouseKeys::empty(), - }) - } - fn input(&mut self, message: InputPreprocessorMessage) { self.handle_message(Message::InputPreprocessor(message)).unwrap(); } From 83043fc95d615e5efd604ec03880221021bfd137 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Mon, 28 Jun 2021 14:33:05 +0200 Subject: [PATCH 10/13] Cleanup --- core/document/src/document.rs | 2 ++ .../src/document/document_message_handler.rs | 25 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 7d255ea411..53bbd761d3 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -133,6 +133,8 @@ impl Document { self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound) } + /// Given a a path to a layer resturns a vector of the incices in the layer tree + /// These indices can be used to order a list of layers pub fn indices_for_path(&self, mut path: &[LayerId]) -> Result, DocumentError> { let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { path = &path[self.work_mount_path.len()..]; diff --git a/core/editor/src/document/document_message_handler.rs b/core/editor/src/document/document_message_handler.rs index 1b14904fdb..a9d2e8872a 100644 --- a/core/editor/src/document/document_message_handler.rs +++ b/core/editor/src/document/document_message_handler.rs @@ -3,7 +3,7 @@ use crate::message_prelude::*; use document_core::layers::Layer; use document_core::{DocumentResponse, LayerId, Operation as DocumentOperation}; use glam::{DAffine2, DVec2}; -use log::{debug, warn}; +use log::warn; use crate::document::Document; use std::collections::VecDeque; @@ -84,28 +84,30 @@ impl DocumentMessageHandler { (!path.is_empty()).then(|| self.handle_folder_changed(path[..path.len() - 1].to_vec())).flatten() } + /// Returns the paths to the selected layers in order fn selected_layers_sorted(&self) -> Vec> { + // Compute the indices for each layer to be able to sort them let mut layers_with_indices: Vec<(Vec, Vec)> = self .active_document() .layer_data .iter() .filter_map(|(path, data)| data.selected.then(|| path.clone())) - .filter_map(|layer_id| { - let indices = self.active_document().document.indices_for_path(&layer_id); - debug!("selected_layers_sorted: layer {:?} became {:?}", layer_id, indices); - - match indices { - Err(e) => { - warn!("selected_layers_sorted: Could not get indices for the layer {:?}: {:?}", layer_id, e); + .filter_map(|path| { + // Currently it is possible that layer_data contains layers that are don't actually exist + // and thus indices_for_path can return an error. We currently skip these layers and log a warning. + // Once this problem is solved this code can be simplified + match self.active_document().document.indices_for_path(&path) { + Err(err) => { + warn!("selected_layers_sorted: Could not get indices for the layer {:?}: {:?}", path, err); None } - Ok(v) => Some((layer_id, v)), + Ok(indices) => Some((path, indices)), } }) .collect(); - layers_with_indices.sort_by_key(|(_, b)| b.clone()); - return layers_with_indices.into_iter().map(|(e, _)| e).collect(); + layers_with_indices.sort_by_key(|(_, indices)| indices.clone()); + return layers_with_indices.into_iter().map(|(path, _)| path).collect(); } } @@ -266,7 +268,6 @@ impl MessageHandler for DocumentMessageHand Err(e) => warn!("Could not access selected layer {:?}: {:?}", path, e), } } - debug!("CopySelectedLayers copied {} layers: {:?}", self.copy_buffer.len(), self.copy_buffer); } PasteLayers => { for layer in self.copy_buffer.iter() { From d82c0fc268c5000ed2bc6e136caab8fe1c5baff3 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Mon, 28 Jun 2021 15:35:24 +0200 Subject: [PATCH 11/13] Add length check to test --- core/editor/src/communication/dispatcher.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/editor/src/communication/dispatcher.rs b/core/editor/src/communication/dispatcher.rs index e19afe98f0..5e519d7083 100644 --- a/core/editor/src/communication/dispatcher.rs +++ b/core/editor/src/communication/dispatcher.rs @@ -246,6 +246,9 @@ mod test { let first_folder_layers_after_copy = layers_after_copy[3].as_folder().unwrap().layers(); let second_folder_layers_after_copy = layers_after_copy[4].as_folder().unwrap().layers(); + assert_eq!(first_folder_layers_after_copy.len(), 2); + assert_eq!(second_folder_layers_after_copy.len(), 2); + assert_eq!(first_folder_layers_after_copy[0], line_before_copy); assert_eq!(first_folder_layers_after_copy[1], pen_before_copy); From 196a04e9c787811dfebb7390d96fdaf47e391d4d Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Thu, 1 Jul 2021 10:24:45 +0200 Subject: [PATCH 12/13] Fix typo --- core/document/src/document.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/document/src/document.rs b/core/document/src/document.rs index 53bbd761d3..acc59f9a2d 100644 --- a/core/document/src/document.rs +++ b/core/document/src/document.rs @@ -133,7 +133,7 @@ impl Document { self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound) } - /// Given a a path to a layer resturns a vector of the incices in the layer tree + /// Given a path to a layer, returns a vector of the indices in the layer tree /// These indices can be used to order a list of layers pub fn indices_for_path(&self, mut path: &[LayerId]) -> Result, DocumentError> { let mut root = if self.is_mounted(self.work_mount_path.as_slice(), path) { From 0840a763e8691df5c945287d691432050baf85a5 Mon Sep 17 00:00:00 2001 From: Till Arnold Date: Fri, 2 Jul 2021 16:54:01 +0200 Subject: [PATCH 13/13] Make mouseup, mousedown more consistent --- core/editor/src/misc/test_utils.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/core/editor/src/misc/test_utils.rs b/core/editor/src/misc/test_utils.rs index 5563e4140c..fe3b03f421 100644 --- a/core/editor/src/misc/test_utils.rs +++ b/core/editor/src/misc/test_utils.rs @@ -19,8 +19,8 @@ pub trait EditorTestUtils { fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32); fn move_mouse(&mut self, x: u32, y: u32); fn mousedown(&mut self, state: MouseState); - fn mouseup(&mut self, x: u32, y: u32); - fn left_mousedown(&mut self, x: u32, y: u32); + fn mouseup(&mut self, state: MouseState); + fn lmb_mousedown(&mut self, x: u32, y: u32); fn input(&mut self, message: InputPreprocessorMessage); fn select_tool(&mut self, typ: ToolType); fn select_primary_color(&mut self, color: Color); @@ -42,9 +42,12 @@ impl EditorTestUtils for Editor { fn drag_tool(&mut self, typ: ToolType, x1: u32, y1: u32, x2: u32, y2: u32) { self.select_tool(typ); self.move_mouse(x1, y1); - self.left_mousedown(x1, y1); + self.lmb_mousedown(x1, y1); self.move_mouse(x2, y2); - self.mouseup(x2, y2); + self.mouseup(MouseState { + position: ViewportPosition { x: x2, y: y2 }, + mouse_keys: MouseKeys::empty(), + }); } fn move_mouse(&mut self, x: u32, y: u32) { @@ -55,15 +58,11 @@ impl EditorTestUtils for Editor { self.input(InputPreprocessorMessage::MouseDown(state)); } - fn mouseup(&mut self, x: u32, y: u32) { - self.handle_message(InputPreprocessorMessage::MouseUp(MouseState { - position: ViewportPosition { x, y }, - mouse_keys: MouseKeys::empty(), - })) - .unwrap() + fn mouseup(&mut self, state: MouseState) { + self.handle_message(InputPreprocessorMessage::MouseUp(state)).unwrap() } - fn left_mousedown(&mut self, x: u32, y: u32) { + fn lmb_mousedown(&mut self, x: u32, y: u32) { self.mousedown(MouseState { position: ViewportPosition { x, y }, mouse_keys: MouseKeys::LEFT,