Copy and paste layers MVP (#220)

* Initial implementation of copy and paste for layers

* Sort layers on copy and add tests

* Fix logger init for test

* Fix `copy_paste_deleted_layers` test

* Readd erroneously removed svg

* Make Layer serializable and cleanup

* Add test for copy and pasting folders

* Cleanup

* Rename left_mouseup

* Cleanup

* Add length check to test

* Fix typo

* Make mouseup, mousedown more consistent
This commit is contained in:
Till Arnold
2021-07-05 00:34:47 +02:00
committed by Keavon Chambers
parent 1d2bb6fb2b
commit 94c42ff5d7
17 changed files with 570 additions and 34 deletions

View File

@@ -10,6 +10,7 @@ license = "Apache-2.0"
[dependencies]
log = "0.4"
kurbo = "0.8"
kurbo = {version="0.8", features = ["serde"]}
serde = { version = "1.0", features = ["derive"] }
glam = "0.16"
glam = { version = "0.16", features = ["serde"] }

View File

@@ -133,6 +133,30 @@ impl Document {
self.folder(path)?.layer(id).ok_or(DocumentError::LayerNotFound)
}
/// 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<Vec<usize>, 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
}
.as_folder()?;
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> {
@@ -227,6 +251,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));

View File

@@ -3,9 +3,10 @@ use kurbo::Shape;
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 {}
impl Ellipse {

View File

@@ -2,9 +2,10 @@ use crate::{DocumentError, LayerId};
use super::{style, 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<LayerId>,

View File

@@ -4,9 +4,10 @@ use kurbo::Point;
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 {}
impl Line {

View File

@@ -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,16 +18,16 @@ pub mod shape;
pub use shape::Shape;
pub mod folder;
pub use folder::Folder;
use crate::DocumentError;
pub use folder::Folder;
use serde::{Deserialize, Serialize};
pub trait LayerData {
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)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum LayerDataTypes {
Folder(Folder),
Ellipse(Ellipse),
@@ -77,11 +78,19 @@ impl LayerDataTypes {
}
}
#[derive(Debug, Clone, PartialEq)]
#[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<String>,
pub data: LayerDataTypes,
#[serde(with = "DAffine2Ref")]
pub transform: glam::DAffine2,
pub style: style::PathStyle,
pub cache: String,

View File

@@ -1,8 +1,9 @@
use serde::{Deserialize, Serialize};
use std::fmt::Write;
use super::{style, LayerData};
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PolyLine {
points: Vec<glam::DVec2>,
}

View File

@@ -4,9 +4,10 @@ use kurbo::Point;
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 Rect {}
impl Rect {

View File

@@ -4,9 +4,10 @@ use kurbo::Vec2;
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 Shape {
equal_sides: bool,
sides: u8,

View File

@@ -1,4 +1,7 @@
use crate::{layers::style, LayerId};
use crate::{
layers::{style, Layer},
LayerId,
};
use serde::{Deserialize, Serialize};
@@ -44,6 +47,10 @@ pub enum Operation {
DuplicateLayer {
path: Vec<LayerId>,
},
PasteLayer {
layer: Layer,
path: Vec<LayerId>,
},
AddFolder {
path: Vec<LayerId>,
},

View File

@@ -19,3 +19,6 @@ glam = "0.16"
[dependencies.document-core]
path = "../document"
package = "graphite-document-core"
[dev-dependencies]
env_logger = "0.8.4"

View File

@@ -76,3 +76,228 @@ impl Dispatcher {
}
}
}
#[cfg(test)]
mod test {
use crate::{
message_prelude::{DocumentMessage, Message},
misc::test_utils::EditorTestUtils,
Editor,
};
use document_core::{color::Color, Operation};
use log::info;
fn init_logger() {
let _ = env_logger::builder().is_test(true).try_init();
}
/// 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() {
init_logger();
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.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);
// 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() {
init_logger();
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.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();
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(), 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]
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];
// 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],
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.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);
assert_eq!(second_folder_layers_after_copy[0], line_before_copy);
assert_eq!(second_folder_layers_after_copy[1], pen_before_copy);
}
#[test]
/// - create rect, shape and ellipse
/// - select ellipse and rect
/// - copy
/// - delete
/// - create another rect
/// - paste
/// - paste
fn copy_paste_deleted_layers() {
init_logger();
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.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();
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.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);
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[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);
}
}

View File

@@ -1,10 +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::warn;
use crate::document::Document;
use std::collections::VecDeque;
@@ -17,6 +16,8 @@ pub enum DocumentMessage {
DeleteLayer(Vec<LayerId>),
DeleteSelectedLayers,
DuplicateSelectedLayers,
CopySelectedLayers,
PasteLayers,
AddFolder(Vec<LayerId>),
RenameLayer(Vec<LayerId>, String),
ToggleLayerVisibility(Vec<LayerId>),
@@ -52,6 +53,7 @@ pub struct DocumentMessageHandler {
active_document: usize,
mmb_down: bool,
mouse_pos: ViewportPosition,
copy_buffer: Vec<Layer>,
}
impl DocumentMessageHandler {
@@ -81,6 +83,32 @@ impl DocumentMessageHandler {
// TODO: Add deduplication
(!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<Vec<LayerId>> {
// Compute the indices for each layer to be able to sort them
let mut layers_with_indices: Vec<(Vec<LayerId>, Vec<usize>)> = self
.active_document()
.layer_data
.iter()
.filter_map(|(path, data)| data.selected.then(|| path.clone()))
.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(indices) => Some((path, indices)),
}
})
.collect();
layers_with_indices.sort_by_key(|(_, indices)| indices.clone());
return layers_with_indices.into_iter().map(|(path, _)| path).collect();
}
}
impl Default for DocumentMessageHandler {
@@ -90,6 +118,7 @@ impl Default for DocumentMessageHandler {
active_document: 0,
mmb_down: false,
mouse_pos: ViewportPosition::default(),
copy_buffer: vec![],
}
}
}
@@ -228,6 +257,25 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
responses.push_back(DocumentOperation::DuplicateLayer { path }.into())
}
}
CopySelectedLayers => {
let paths: Vec<Vec<LayerId>> = self.selected_layers_sorted();
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),
}
}
}
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 {
@@ -294,9 +342,9 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
}
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, MouseMove, TranslateUp, TranslateDown)
actions!(DocumentMessageDiscriminant; Undo, DeleteSelectedLayers, DuplicateSelectedLayers, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown, CopySelectedLayers, PasteLayers, )
} else {
actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown)
actions!(DocumentMessageDiscriminant; Undo, RenderDocument, ExportDocument, NewDocument, CloseActiveDocument, NextDocument, PrevDocument, MouseMove, TranslateUp, TranslateDown, PasteLayers)
}
}
}

View File

@@ -103,6 +103,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},
@@ -174,11 +175,12 @@ impl Default for Mapping {
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]},
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 }
}

View File

@@ -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::*;

View File

@@ -0,0 +1,83 @@
use crate::{
input::{
mouse::{MouseKeys, MouseState, ViewportPosition},
InputPreprocessorMessage,
},
message_prelude::{Message, ToolMessage},
tool::ToolType,
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);
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 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);
}
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.lmb_mousedown(x1, y1);
self.move_mouse(x2, y2);
self.mouseup(MouseState {
position: ViewportPosition { x: x2, y: y2 },
mouse_keys: MouseKeys::empty(),
});
}
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.handle_message(InputPreprocessorMessage::MouseUp(state)).unwrap()
}
fn lmb_mousedown(&mut self, x: u32, y: u32) {
self.mousedown(MouseState {
position: ViewportPosition { x, y },
mouse_keys: MouseKeys::LEFT,
})
}
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();
}
}