mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 15:08:13 +08:00
Implement anchor and handle point rendering with the Path Tool (#353)
* Implement Path Tool * Draw a red rectangle where the first point on the shape is * Correctly render anchors, handles, and connecting lines * Fix drain() which can panic * Refactor frontend messages to work as return values not callbacks * Reduce the number of unnecessary frontend updates * Fix stack overflow by using a loop * Group Document Render calls and put them at the end * Speed hacks for dirtification * Add performance * Bunch folder changed updates * Add triggers to redraw overlays to movement_handler * Polish the pixel-perfect rendering of vector manipulators * Restore scrollbars that were disabled * Cleanup * WIP Add shape outline rendering * Fix compiling * Add outlines to selected shapes * Fix outlines rendering over handles and anchors * Fix dirtification * Add a comment * Address code review feedback * Formatting * Small tweaks Co-authored-by: Oliver Davies <oliver@psyfer.io> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Oliver Davies
Dennis Kobert
parent
8336b3e809
commit
913b9365e2
Generated
+1
@@ -97,6 +97,7 @@ dependencies = [
|
|||||||
"glam",
|
"glam",
|
||||||
"graphite-graphene",
|
"graphite-graphene",
|
||||||
"graphite-proc-macros",
|
"graphite-proc-macros",
|
||||||
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
+7
-1
@@ -8,4 +8,10 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[profile.release.package.graphite-wasm]
|
[profile.release.package.graphite-wasm]
|
||||||
opt-level = "s"
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev.package.graphite-wasm]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 3
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ graphite-proc-macros = { path = "../proc-macros" }
|
|||||||
glam = { version="0.17", features = ["serde"] }
|
glam = { version="0.17", features = ["serde"] }
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
spin = "0.9.2"
|
spin = "0.9.2"
|
||||||
|
kurbo = { git = "https://github.com/GraphiteEditor/kurbo.git", features = [
|
||||||
|
"serde",
|
||||||
|
] }
|
||||||
|
|
||||||
[dependencies.graphene]
|
[dependencies.graphene]
|
||||||
path = "../graphene"
|
path = "../graphene"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{frontend::FrontendMessageHandler, message_prelude::*, Callback, EditorError};
|
use crate::{message_prelude::*, EditorError};
|
||||||
|
|
||||||
pub use crate::document::DocumentsMessageHandler;
|
pub use crate::document::DocumentsMessageHandler;
|
||||||
pub use crate::input::{InputMapper, InputPreprocessor};
|
pub use crate::input::{InputMapper, InputPreprocessor};
|
||||||
@@ -8,49 +8,47 @@ use crate::global::GlobalMessageHandler;
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
frontend_message_handler: FrontendMessageHandler,
|
|
||||||
input_preprocessor: InputPreprocessor,
|
input_preprocessor: InputPreprocessor,
|
||||||
input_mapper: InputMapper,
|
input_mapper: InputMapper,
|
||||||
global_message_handler: GlobalMessageHandler,
|
global_message_handler: GlobalMessageHandler,
|
||||||
tool_message_handler: ToolMessageHandler,
|
tool_message_handler: ToolMessageHandler,
|
||||||
documents_message_handler: DocumentsMessageHandler,
|
documents_message_handler: DocumentsMessageHandler,
|
||||||
messages: VecDeque<Message>,
|
messages: VecDeque<Message>,
|
||||||
|
pub responses: Vec<FrontendMessage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GROUP_MESSAGES: &[MessageDiscriminant] = &[
|
||||||
|
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
||||||
|
MessageDiscriminant::Documents(DocumentsMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
|
||||||
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateLayer),
|
||||||
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::ExpandFolder),
|
||||||
|
MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectedLayersChanged),
|
||||||
|
];
|
||||||
|
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
|
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
|
||||||
let message = message.into();
|
self.messages.push_back(message.into());
|
||||||
|
|
||||||
use Message::*;
|
use Message::*;
|
||||||
if !(matches!(
|
while let Some(message) = self.messages.pop_front() {
|
||||||
message,
|
if GROUP_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) {
|
||||||
Message::InputPreprocessor(_)
|
continue;
|
||||||
| Message::InputMapper(_)
|
}
|
||||||
| Message::Documents(DocumentsMessage::Document(DocumentMessage::RenderDocument))
|
log_message(&message);
|
||||||
| Message::Frontend(FrontendMessage::UpdateCanvas { .. })
|
match message {
|
||||||
| Message::Frontend(FrontendMessage::UpdateScrollbars { .. })
|
NoOp => (),
|
||||||
| Message::Frontend(FrontendMessage::SetCanvasZoom { .. })
|
Documents(message) => self.documents_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
|
||||||
| Message::Frontend(FrontendMessage::SetCanvasRotation { .. })
|
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
|
||||||
) || MessageDiscriminant::from(&message).local_name().ends_with("MouseMove"))
|
Tool(message) => self
|
||||||
{
|
.tool_message_handler
|
||||||
log::trace!("Message: {:?}", message);
|
.process_action(message, (self.documents_message_handler.active_document(), &self.input_preprocessor), &mut self.messages),
|
||||||
//log::trace!("Hints:{:?}", self.input_mapper.hints(self.collect_actions()));
|
Frontend(message) => self.responses.push(message),
|
||||||
}
|
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
|
||||||
match message {
|
InputMapper(message) => {
|
||||||
NoOp => (),
|
let actions = self.collect_actions();
|
||||||
Documents(message) => self.documents_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
|
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
|
||||||
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
|
}
|
||||||
Tool(message) => self
|
|
||||||
.tool_message_handler
|
|
||||||
.process_action(message, (self.documents_message_handler.active_document(), &self.input_preprocessor), &mut self.messages),
|
|
||||||
Frontend(message) => self.frontend_message_handler.process_action(message, (), &mut self.messages),
|
|
||||||
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
|
|
||||||
InputMapper(message) => {
|
|
||||||
let actions = self.collect_actions();
|
|
||||||
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(message) = self.messages.pop_front() {
|
|
||||||
self.handle_message(message)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -58,7 +56,6 @@ impl Dispatcher {
|
|||||||
pub fn collect_actions(&self) -> ActionList {
|
pub fn collect_actions(&self) -> ActionList {
|
||||||
//TODO: reduce the number of heap allocations
|
//TODO: reduce the number of heap allocations
|
||||||
let mut list = Vec::new();
|
let mut list = Vec::new();
|
||||||
list.extend(self.frontend_message_handler.actions());
|
|
||||||
list.extend(self.input_preprocessor.actions());
|
list.extend(self.input_preprocessor.actions());
|
||||||
list.extend(self.input_mapper.actions());
|
list.extend(self.input_mapper.actions());
|
||||||
list.extend(self.global_message_handler.actions());
|
list.extend(self.global_message_handler.actions());
|
||||||
@@ -67,24 +64,36 @@ impl Dispatcher {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(callback: Callback) -> Dispatcher {
|
pub fn new() -> Dispatcher {
|
||||||
Dispatcher {
|
Dispatcher {
|
||||||
frontend_message_handler: FrontendMessageHandler::new(callback),
|
|
||||||
input_preprocessor: InputPreprocessor::default(),
|
input_preprocessor: InputPreprocessor::default(),
|
||||||
global_message_handler: GlobalMessageHandler::new(),
|
global_message_handler: GlobalMessageHandler::new(),
|
||||||
input_mapper: InputMapper::default(),
|
input_mapper: InputMapper::default(),
|
||||||
documents_message_handler: DocumentsMessageHandler::default(),
|
documents_message_handler: DocumentsMessageHandler::default(),
|
||||||
tool_message_handler: ToolMessageHandler::default(),
|
tool_message_handler: ToolMessageHandler::default(),
|
||||||
messages: VecDeque::new(),
|
messages: VecDeque::new(),
|
||||||
|
responses: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_message(message: &Message) {
|
||||||
|
use Message::*;
|
||||||
|
if log::max_level() == log::LevelFilter::Trace
|
||||||
|
&& !(matches!(
|
||||||
|
message,
|
||||||
|
InputPreprocessor(_) | Frontend(FrontendMessage::SetCanvasZoom { .. }) | Frontend(FrontendMessage::SetCanvasRotation { .. })
|
||||||
|
) || MessageDiscriminant::from(message).local_name().ends_with("MouseMove"))
|
||||||
|
{
|
||||||
|
log::trace!("Message: {:?}", message);
|
||||||
|
//log::trace!("Hints:{:?}", self.input_mapper.hints(self.collect_actions()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::{document::DocumentMessageHandler, message_prelude::*, misc::test_utils::EditorTestUtils, Editor};
|
use crate::{document::DocumentMessageHandler, message_prelude::*, misc::test_utils::EditorTestUtils, Editor};
|
||||||
use graphene::{color::Color, Operation};
|
use graphene::{color::Color, Operation};
|
||||||
use log::info;
|
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
@@ -95,9 +104,7 @@ mod test {
|
|||||||
/// 2. A blue shape
|
/// 2. A blue shape
|
||||||
/// 3. A green ellipse
|
/// 3. A green ellipse
|
||||||
fn create_editor_with_three_layers() -> Editor {
|
fn create_editor_with_three_layers() -> Editor {
|
||||||
let mut editor = Editor::new(Box::new(|e| {
|
let mut editor = Editor::new();
|
||||||
info!("Got frontend message: {:?}", e);
|
|
||||||
}));
|
|
||||||
|
|
||||||
editor.select_primary_color(Color::RED);
|
editor.select_primary_color(Color::RED);
|
||||||
editor.draw_rect(100., 200., 300., 400.);
|
editor.draw_rect(100., 200., 300., 400.);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use graphite_proc_macros::*;
|
use graphite_proc_macros::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::hash_map::DefaultHasher,
|
collections::hash_map::DefaultHasher,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
@@ -16,7 +17,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message]
|
#[impl_message]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
NoOp,
|
NoOp,
|
||||||
#[child]
|
#[child]
|
||||||
|
|||||||
+11
-3
@@ -1,3 +1,5 @@
|
|||||||
|
use graphene::color::Color;
|
||||||
|
|
||||||
// VIEWPORT
|
// VIEWPORT
|
||||||
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
|
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
|
||||||
pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;
|
pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;
|
||||||
@@ -13,12 +15,15 @@ pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
|
|||||||
|
|
||||||
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
|
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
|
||||||
|
|
||||||
|
// SELECT TOOL
|
||||||
|
pub const SELECTION_TOLERANCE: f64 = 1.;
|
||||||
|
|
||||||
|
// PATH TOOL
|
||||||
|
pub const VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE: f64 = 5.;
|
||||||
|
|
||||||
// LINE TOOL
|
// LINE TOOL
|
||||||
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||||
|
|
||||||
// SELECT TOOL
|
|
||||||
pub const SELECTION_TOLERANCE: f64 = 1.0;
|
|
||||||
|
|
||||||
// SCROLLBARS
|
// SCROLLBARS
|
||||||
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
||||||
pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
|
pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
|
||||||
@@ -27,3 +32,6 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
|||||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||||
pub const FILE_EXPORT_SUFFIX: &str = ".svg";
|
pub const FILE_EXPORT_SUFFIX: &str = ".svg";
|
||||||
|
|
||||||
|
// COLORS
|
||||||
|
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use crate::{
|
|||||||
EditorError,
|
EditorError,
|
||||||
};
|
};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene::{document::Document as InternalDocument, DocumentError, LayerId};
|
use graphene::{document::Document as InternalDocument, layers::LayerDataType, DocumentError, LayerId};
|
||||||
|
use kurbo::PathSeg;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -41,6 +42,20 @@ pub enum AlignAggregate {
|
|||||||
Average,
|
Average,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub enum VectorManipulatorSegment {
|
||||||
|
Line(DVec2, DVec2),
|
||||||
|
Quad(DVec2, DVec2, DVec2),
|
||||||
|
Cubic(DVec2, DVec2, DVec2, DVec2),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub struct VectorManipulatorShape {
|
||||||
|
pub path: kurbo::BezPath,
|
||||||
|
pub segments: Vec<VectorManipulatorSegment>,
|
||||||
|
pub transform: DAffine2,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DocumentMessageHandler {
|
pub struct DocumentMessageHandler {
|
||||||
pub document: InternalDocument,
|
pub document: InternalDocument,
|
||||||
@@ -65,7 +80,7 @@ impl Default for DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, DocumentsMessage, Document)]
|
#[impl_message(Message, DocumentsMessage, Document)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum DocumentMessage {
|
pub enum DocumentMessage {
|
||||||
#[child]
|
#[child]
|
||||||
Movement(MovementMessage),
|
Movement(MovementMessage),
|
||||||
@@ -112,6 +127,7 @@ impl From<DocumentOperation> for DocumentMessage {
|
|||||||
Self::DispatchOperation(Box::new(operation))
|
Self::DispatchOperation(Box::new(operation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DocumentOperation> for Message {
|
impl From<DocumentOperation> for Message {
|
||||||
fn from(operation: DocumentOperation) -> Message {
|
fn from(operation: DocumentOperation) -> Message {
|
||||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||||
@@ -119,11 +135,6 @@ impl From<DocumentOperation> for Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentMessageHandler {
|
impl DocumentMessageHandler {
|
||||||
fn filter_document_responses(&self, document_responses: &mut Vec<DocumentResponse>) -> bool {
|
|
||||||
let len = document_responses.len();
|
|
||||||
document_responses.retain(|response| !matches!(response, DocumentResponse::DocumentChanged));
|
|
||||||
document_responses.len() != len
|
|
||||||
}
|
|
||||||
pub fn handle_folder_changed(&mut self, path: Vec<LayerId>) -> Option<Message> {
|
pub fn handle_folder_changed(&mut self, path: Vec<LayerId>) -> Option<Message> {
|
||||||
let _ = self.document.render_root();
|
let _ = self.document.render_root();
|
||||||
self.layer_data(&path).expanded.then(|| {
|
self.layer_data(&path).expanded.then(|| {
|
||||||
@@ -131,9 +142,11 @@ impl DocumentMessageHandler {
|
|||||||
FrontendMessage::ExpandFolder { path, children }.into()
|
FrontendMessage::ExpandFolder { path, children }.into()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_selection(&mut self) {
|
fn clear_selection(&mut self) {
|
||||||
self.layer_data.values_mut().for_each(|layer_data| layer_data.selected = false);
|
self.layer_data.values_mut().for_each(|layer_data| layer_data.selected = false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
|
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
|
||||||
if self.document.layer(path).ok()?.overlay {
|
if self.document.layer(path).ok()?.overlay {
|
||||||
return None;
|
return None;
|
||||||
@@ -143,13 +156,51 @@ impl DocumentMessageHandler {
|
|||||||
// TODO: Add deduplication
|
// TODO: Add deduplication
|
||||||
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { path: path.to_vec(), data }.into())
|
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { path: path.to_vec(), data }.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||||
let paths = self.selected_layers().map(|vec| &vec[..]);
|
let paths = self.selected_layers().map(|vec| &vec[..]);
|
||||||
self.document.combined_viewport_bounding_box(paths)
|
self.document.combined_viewport_bounding_box(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Consider moving this to some kind of overlay manager in the future
|
||||||
|
pub fn selected_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
|
||||||
|
let shapes = self.selected_layers().filter_map(|path_to_shape| {
|
||||||
|
let viewport_transform = self.document.generate_transform_relative_to_viewport(path_to_shape.as_slice()).ok()?;
|
||||||
|
|
||||||
|
let shape = match &self.document.layer(path_to_shape.as_slice()).ok()?.data {
|
||||||
|
LayerDataType::Shape(shape) => Some(shape),
|
||||||
|
LayerDataType::Folder(_) => None,
|
||||||
|
}?;
|
||||||
|
let path = shape.path.clone();
|
||||||
|
|
||||||
|
let segments = path
|
||||||
|
.segments()
|
||||||
|
.map(|segment| -> VectorManipulatorSegment {
|
||||||
|
let place = |point: kurbo::Point| -> DVec2 { viewport_transform.transform_point2(DVec2::from((point.x, point.y))) };
|
||||||
|
|
||||||
|
match segment {
|
||||||
|
PathSeg::Line(line) => VectorManipulatorSegment::Line(place(line.p0), place(line.p1)),
|
||||||
|
PathSeg::Quad(quad) => VectorManipulatorSegment::Quad(place(quad.p0), place(quad.p1), place(quad.p2)),
|
||||||
|
PathSeg::Cubic(cubic) => VectorManipulatorSegment::Cubic(place(cubic.p0), place(cubic.p1), place(cubic.p2), place(cubic.p3)),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<VectorManipulatorSegment>>();
|
||||||
|
|
||||||
|
Some(VectorManipulatorShape {
|
||||||
|
path,
|
||||||
|
segments,
|
||||||
|
transform: viewport_transform,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Consider refactoring this in a way that avoids needing to collect() so we can skip the heap allocations
|
||||||
|
shapes.collect::<Vec<VectorManipulatorShape>>()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn layerdata(&self, path: &[LayerId]) -> &LayerData {
|
pub fn layerdata(&self, path: &[LayerId]) -> &LayerData {
|
||||||
self.layer_data.get(path).expect("Layerdata does not exist")
|
self.layer_data.get(path).expect("Layerdata does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layerdata_mut(&mut self, path: &[LayerId]) -> &mut LayerData {
|
pub fn layerdata_mut(&mut self, path: &[LayerId]) -> &mut LayerData {
|
||||||
self.layer_data.entry(path.to_vec()).or_insert_with(|| LayerData::new(true))
|
self.layer_data.entry(path.to_vec()).or_insert_with(|| LayerData::new(true))
|
||||||
}
|
}
|
||||||
@@ -200,6 +251,7 @@ impl DocumentMessageHandler {
|
|||||||
pub fn non_selected_layers_sorted(&self) -> Vec<Vec<LayerId>> {
|
pub fn non_selected_layers_sorted(&self) -> Vec<Vec<LayerId>> {
|
||||||
self.layers_sorted(Some(false))
|
self.layers_sorted(Some(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_name(name: String) -> Self {
|
pub fn with_name(name: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
document: InternalDocument::default(),
|
document: InternalDocument::default(),
|
||||||
@@ -210,6 +262,7 @@ impl DocumentMessageHandler {
|
|||||||
movement_handler: MovementMessageHandler::default(),
|
movement_handler: MovementMessageHandler::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
|
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
|
||||||
let mut document = Self::with_name(name);
|
let mut document = Self::with_name(name);
|
||||||
let internal_document = InternalDocument::with_content(&serialized_content);
|
let internal_document = InternalDocument::with_content(&serialized_content);
|
||||||
@@ -271,7 +324,6 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> {
|
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> {
|
||||||
self.document.render_root();
|
|
||||||
let data: LayerData = *layer_data(&mut self.layer_data, &path);
|
let data: LayerData = *layer_data(&mut self.layer_data, &path);
|
||||||
let layer = self.document.layer(&path)?;
|
let layer = self.document.layer(&path)?;
|
||||||
let entry = layer_panel_entry(&data, self.document.multiply_transforms(&path).unwrap(), layer, path);
|
let entry = layer_panel_entry(&data, self.document.multiply_transforms(&path).unwrap(), layer, path);
|
||||||
@@ -372,22 +424,25 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToggleLayerVisibility(path) => {
|
ToggleLayerVisibility(path) => {
|
||||||
responses.push_back(DocumentOperation::ToggleVisibility { path }.into());
|
responses.push_back(DocumentOperation::ToggleLayerVisibility { path }.into());
|
||||||
}
|
}
|
||||||
ToggleLayerExpansion(path) => {
|
ToggleLayerExpansion(path) => {
|
||||||
self.layer_data(&path).expanded ^= true;
|
self.layer_data(&path).expanded ^= true;
|
||||||
responses.extend(self.handle_folder_changed(path));
|
responses.push_back(FolderChanged(path).into());
|
||||||
|
}
|
||||||
|
SelectionChanged => {
|
||||||
|
// TODO: Hoist this duplicated code into wider system
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
SelectionChanged => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
|
||||||
DeleteSelectedLayers => {
|
DeleteSelectedLayers => {
|
||||||
self.backup();
|
self.backup();
|
||||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_front(ToolMessage::SelectedLayersChanged.into());
|
||||||
for path in self.selected_layers().cloned() {
|
for path in self.selected_layers().cloned() {
|
||||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ClearOverlays => {
|
ClearOverlays => {
|
||||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
for path in self.layer_data.keys().filter(|path| self.document.layer(path).unwrap().overlay).cloned() {
|
for path in self.layer_data.keys().filter(|path| self.document.layer(path).unwrap().overlay).cloned() {
|
||||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||||
}
|
}
|
||||||
@@ -407,8 +462,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
responses.extend(self.select_layer(&path));
|
responses.extend(self.select_layer(&path));
|
||||||
}
|
}
|
||||||
// TODO: Correctly update layer panel in clear_selection instead of here
|
// TODO: Correctly update layer panel in clear_selection instead of here
|
||||||
responses.extend(self.handle_folder_changed(Vec::new()));
|
responses.push_back(FolderChanged(Vec::new()).into());
|
||||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
SelectAllLayers => {
|
SelectAllLayers => {
|
||||||
let all_layer_paths = self
|
let all_layer_paths = self
|
||||||
@@ -427,46 +482,41 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
Undo => {
|
Undo => {
|
||||||
responses.push_back(SelectMessage::Abort.into());
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
responses.push_back(DocumentHistoryBackward.into());
|
responses.push_back(DocumentHistoryBackward.into());
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
responses.push_back(RenderDocument.into());
|
responses.push_back(RenderDocument.into());
|
||||||
responses.push_back(FolderChanged(vec![]).into());
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
}
|
}
|
||||||
Redo => {
|
Redo => {
|
||||||
responses.push_back(SelectMessage::Abort.into());
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
responses.push_back(DocumentHistoryForward.into());
|
responses.push_back(DocumentHistoryForward.into());
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
responses.push_back(RenderDocument.into());
|
responses.push_back(RenderDocument.into());
|
||||||
responses.push_back(FolderChanged(vec![]).into());
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
}
|
}
|
||||||
FolderChanged(path) => responses.extend(self.handle_folder_changed(path)),
|
FolderChanged(path) => responses.extend(self.handle_folder_changed(path)),
|
||||||
DispatchOperation(op) => match self.document.handle_operation(&op) {
|
DispatchOperation(op) => match self.document.handle_operation(&op) {
|
||||||
Ok(Some(mut document_responses)) => {
|
Ok(Some(document_responses)) => {
|
||||||
let canvas_dirty = self.filter_document_responses(&mut document_responses);
|
|
||||||
responses.extend(
|
responses.extend(
|
||||||
document_responses
|
document_responses
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|response| match response {
|
.map(|response| match response {
|
||||||
DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path),
|
DocumentResponse::FolderChanged { path } => Some(FolderChanged(path).into()),
|
||||||
DocumentResponse::DeletedLayer { path } => {
|
DocumentResponse::DeletedLayer { path } => {
|
||||||
self.layer_data.remove(&path);
|
self.layer_data.remove(&path);
|
||||||
|
Some(ToolMessage::SelectedLayersChanged.into())
|
||||||
Some(SelectMessage::UpdateSelectionBoundingBox.into())
|
|
||||||
}
|
}
|
||||||
DocumentResponse::LayerChanged { path } => Some(
|
DocumentResponse::LayerChanged { path } => (!self.document.layer(&path).unwrap().overlay).then(|| {
|
||||||
FrontendMessage::UpdateLayer {
|
FrontendMessage::UpdateLayer {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
data: self.layer_panel_entry(path).unwrap(),
|
data: self.layer_panel_entry(path).unwrap(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into()
|
||||||
),
|
}),
|
||||||
DocumentResponse::CreatedLayer { path } => (!self.document.layer(&path).unwrap().overlay).then(|| SetSelectedLayers(vec![path]).into()),
|
DocumentResponse::CreatedLayer { path } => (!self.document.layer(&path).unwrap().overlay).then(|| SetSelectedLayers(vec![path]).into()),
|
||||||
DocumentResponse::DocumentChanged => unreachable!(),
|
DocumentResponse::DocumentChanged => Some(RenderDocument.into()),
|
||||||
})
|
})
|
||||||
.flatten(),
|
.flatten(),
|
||||||
);
|
);
|
||||||
if canvas_dirty {
|
|
||||||
responses.push_back(RenderDocument.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => log::error!("DocumentError: {:?}", e),
|
Err(e) => log::error!("DocumentError: {:?}", e),
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
@@ -478,6 +528,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let scale = 0.5 + ASYMPTOTIC_EFFECT + self.layerdata(&[]).scale * SCALE_EFFECT;
|
let scale = 0.5 + ASYMPTOTIC_EFFECT + self.layerdata(&[]).scale * SCALE_EFFECT;
|
||||||
let viewport_size = ipp.viewport_bounds.size();
|
let viewport_size = ipp.viewport_bounds.size();
|
||||||
let viewport_mid = ipp.viewport_bounds.center();
|
let viewport_mid = ipp.viewport_bounds.center();
|
||||||
@@ -507,7 +558,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
};
|
};
|
||||||
responses.push_back(operation.into());
|
responses.push_back(operation.into());
|
||||||
}
|
}
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
MoveSelectedLayersTo { path, insert_index } => {
|
MoveSelectedLayersTo { path, insert_index } => {
|
||||||
responses.push_back(DocumentsMessage::CopySelectedLayers.into());
|
responses.push_back(DocumentsMessage::CopySelectedLayers.into());
|
||||||
@@ -564,7 +615,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AlignSelectedLayers(axis, aggregate) => {
|
AlignSelectedLayers(axis, aggregate) => {
|
||||||
@@ -598,12 +649,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
let mut common = actions!(DocumentMessageDiscriminant;
|
let mut common = actions!(DocumentMessageDiscriminant;
|
||||||
Undo,
|
Undo,
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use graphene::layers::Layer;
|
|||||||
use graphene::{LayerId, Operation as DocumentOperation};
|
use graphene::{LayerId, Operation as DocumentOperation};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use super::DocumentMessageHandler;
|
use super::DocumentMessageHandler;
|
||||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||||
|
|
||||||
#[impl_message(Message, Documents)]
|
#[impl_message(Message, Documents)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum DocumentsMessage {
|
pub enum DocumentsMessage {
|
||||||
CopySelectedLayers,
|
CopySelectedLayers,
|
||||||
PasteLayers {
|
PasteLayers {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ mod movement_handler;
|
|||||||
pub use document_file::LayerData;
|
pub use document_file::LayerData;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis};
|
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_message_handler::{DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
pub use document_message_handler::{DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ use glam::DVec2;
|
|||||||
use graphene::document::Document;
|
use graphene::document::Document;
|
||||||
use graphene::Operation as DocumentOperation;
|
use graphene::Operation as DocumentOperation;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[impl_message(Message, DocumentMessage, Movement)]
|
#[impl_message(Message, DocumentMessage, Movement)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum MovementMessage {
|
pub enum MovementMessage {
|
||||||
MouseMove,
|
MouseMove,
|
||||||
TranslateCanvasBegin,
|
TranslateCanvasBegin,
|
||||||
@@ -91,6 +92,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||||
|
|
||||||
layerdata.translation += transformed_delta;
|
layerdata.translation += transformed_delta;
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
if self.rotating {
|
if self.rotating {
|
||||||
@@ -105,7 +107,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
|
|
||||||
layerdata.rotation += rotation;
|
layerdata.rotation += rotation;
|
||||||
layerdata.snap_rotate = snapping;
|
layerdata.snap_rotate = snapping;
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
FrontendMessage::SetCanvasRotation {
|
FrontendMessage::SetCanvasRotation {
|
||||||
new_radians: layerdata.snapped_angle(),
|
new_radians: layerdata.snapped_angle(),
|
||||||
@@ -121,6 +123,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
let new = (layerdata.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
let new = (layerdata.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||||
layerdata.scale = new;
|
layerdata.scale = new;
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
self.mouse_pos = ipp.mouse.position;
|
self.mouse_pos = ipp.mouse.position;
|
||||||
@@ -128,16 +131,19 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
SetCanvasZoom(new) => {
|
SetCanvasZoom(new) => {
|
||||||
layerdata.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
layerdata.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
IncreaseCanvasZoom => {
|
IncreaseCanvasZoom => {
|
||||||
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > layerdata.scale).unwrap_or(&layerdata.scale);
|
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > layerdata.scale).unwrap_or(&layerdata.scale);
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
DecreaseCanvasZoom => {
|
DecreaseCanvasZoom => {
|
||||||
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < layerdata.scale).unwrap_or(&layerdata.scale);
|
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < layerdata.scale).unwrap_or(&layerdata.scale);
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
WheelCanvasZoom => {
|
WheelCanvasZoom => {
|
||||||
@@ -158,6 +164,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
layerdata.scale = new;
|
layerdata.scale = new;
|
||||||
layerdata.translation += transformed_delta;
|
layerdata.translation += transformed_delta;
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
WheelCanvasTranslate { use_y_as_x } => {
|
WheelCanvasTranslate { use_y_as_x } => {
|
||||||
@@ -167,13 +174,14 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
} * VIEWPORT_SCROLL_RATE;
|
} * VIEWPORT_SCROLL_RATE;
|
||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||||
layerdata.translation += transformed_delta;
|
layerdata.translation += transformed_delta;
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
SetCanvasRotation(new) => {
|
SetCanvasRotation(new) => {
|
||||||
layerdata.rotation = new;
|
layerdata.rotation = new;
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
ZoomCanvasToFitAll => {
|
ZoomCanvasToFitAll => {
|
||||||
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
||||||
@@ -190,6 +198,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
layerdata.translation += center;
|
layerdata.translation += center;
|
||||||
layerdata.scale *= new_scale;
|
layerdata.scale *= new_scale;
|
||||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,12 +206,14 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
|||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||||
|
|
||||||
layerdata.translation += transformed_delta;
|
layerdata.translation += transformed_delta;
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
TranslateCanvasByViewportFraction(delta) => {
|
TranslateCanvasByViewportFraction(delta) => {
|
||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||||
|
|
||||||
layerdata.translation += transformed_delta;
|
layerdata.translation += transformed_delta;
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ use crate::tool::tool_options::ToolOptions;
|
|||||||
use crate::Color;
|
use crate::Color;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub type Callback = Box<dyn Fn(FrontendMessage)>;
|
|
||||||
|
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
@@ -29,34 +27,3 @@ pub enum FrontendMessage {
|
|||||||
SetCanvasZoom { new_zoom: f64 },
|
SetCanvasZoom { new_zoom: f64 },
|
||||||
SetCanvasRotation { new_radians: f64 },
|
SetCanvasRotation { new_radians: f64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrontendMessageHandler {
|
|
||||||
callback: crate::Callback,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FrontendMessageHandler {
|
|
||||||
pub fn new(callback: Callback) -> Self {
|
|
||||||
Self { callback }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MessageHandler<FrontendMessage, ()> for FrontendMessageHandler {
|
|
||||||
fn process_action(&mut self, message: FrontendMessage, _data: (), _responses: &mut VecDeque<Message>) {
|
|
||||||
(self.callback)(message)
|
|
||||||
}
|
|
||||||
advertise_actions!(
|
|
||||||
FrontendMessageDiscriminant;
|
|
||||||
|
|
||||||
DisplayError,
|
|
||||||
CollapseFolder,
|
|
||||||
ExpandFolder,
|
|
||||||
SetActiveTool,
|
|
||||||
UpdateCanvas,
|
|
||||||
UpdateScrollbars,
|
|
||||||
EnableTextInput,
|
|
||||||
DisableTextInput,
|
|
||||||
SetCanvasZoom,
|
|
||||||
SetCanvasRotation,
|
|
||||||
OpenDocumentBrowse,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
pub mod frontend_message_handler;
|
pub mod frontend_message_handler;
|
||||||
pub mod layer_panel;
|
pub mod layer_panel;
|
||||||
|
|
||||||
pub use frontend_message_handler::{Callback, FrontendMessage, FrontendMessageDiscriminant, FrontendMessageHandler};
|
pub use frontend_message_handler::{FrontendMessage, FrontendMessageDiscriminant};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[impl_message(Message, Global)]
|
#[impl_message(Message, Global)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum GlobalMessage {
|
pub enum GlobalMessage {
|
||||||
LogInfo,
|
LogInfo,
|
||||||
LogDebug,
|
LogDebug,
|
||||||
|
|||||||
@@ -7,13 +7,14 @@ use super::{
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::ToolType;
|
use crate::tool::ToolType;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
const NUDGE_AMOUNT: f64 = 1.;
|
const NUDGE_AMOUNT: f64 = 1.;
|
||||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
||||||
|
|
||||||
#[impl_message(Message, InputMapper)]
|
#[impl_message(Message, InputMapper)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum InputMapperMessage {
|
pub enum InputMapperMessage {
|
||||||
PointerMove,
|
PointerMove,
|
||||||
MouseScroll,
|
MouseScroll,
|
||||||
@@ -172,14 +173,16 @@ impl Default for Mapping {
|
|||||||
// Fill
|
// Fill
|
||||||
entry! {action=FillMessage::MouseDown, key_down=Lmb},
|
entry! {action=FillMessage::MouseDown, key_down=Lmb},
|
||||||
// Tool Actions
|
// Tool Actions
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Fill), key_down=KeyF},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Select), key_down=KeyV},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Eyedropper), key_down=KeyI},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Fill), key_down=KeyF},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Path), key_down=KeyA},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Line), key_down=KeyL},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Pen), key_down=KeyP},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Pen), key_down=KeyP},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Line), key_down=KeyL},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Shape), key_down=KeyY},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Rectangle), key_down=KeyM},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Eyedropper), key_down=KeyI},
|
entry! {action=ToolMessage::ActivateTool(ToolType::Ellipse), key_down=KeyE},
|
||||||
|
entry! {action=ToolMessage::ActivateTool(ToolType::Shape), key_down=KeyY},
|
||||||
|
// Colors
|
||||||
entry! {action=ToolMessage::ResetColors, key_down=KeyX, modifiers=[KeyShift, KeyControl]},
|
entry! {action=ToolMessage::ResetColors, key_down=KeyX, modifiers=[KeyShift, KeyControl]},
|
||||||
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
entry! {action=ToolMessage::SwapColors, key_down=KeyX, modifiers=[KeyShift]},
|
||||||
// Editor Actions
|
// Editor Actions
|
||||||
@@ -300,7 +303,7 @@ impl InputMapper {
|
|||||||
let mut actions = actions
|
let mut actions = actions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::SelectTool) | MessageDiscriminant::Global(_)));
|
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Global(_)));
|
||||||
self.mapping
|
self.mapping
|
||||||
.key_down
|
.key_down
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ use bitflags::bitflags;
|
|||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use graphene::DocumentResponse;
|
pub use graphene::DocumentResponse;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[impl_message(Message, InputPreprocessor)]
|
#[impl_message(Message, InputPreprocessor)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum InputPreprocessorMessage {
|
pub enum InputPreprocessorMessage {
|
||||||
MouseDown(EditorMouseState, ModifierKeys),
|
MouseDown(EditorMouseState, ModifierKeys),
|
||||||
MouseUp(EditorMouseState, ModifierKeys),
|
MouseUp(EditorMouseState, ModifierKeys),
|
||||||
@@ -21,7 +22,7 @@ pub enum InputPreprocessorMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct ModifierKeys: u8 {
|
pub struct ModifierKeys: u8 {
|
||||||
const CONTROL = 0b0000_0001;
|
const CONTROL = 0b0000_0001;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
// Origin is top left
|
// Origin is top left
|
||||||
pub type ViewportPosition = DVec2;
|
pub type ViewportPosition = DVec2;
|
||||||
pub type EditorPosition = DVec2;
|
pub type EditorPosition = DVec2;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Default)]
|
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct ViewportBounds {
|
pub struct ViewportBounds {
|
||||||
pub top_left: DVec2,
|
pub top_left: DVec2,
|
||||||
pub bottom_right: DVec2,
|
pub bottom_right: DVec2,
|
||||||
@@ -28,7 +29,7 @@ impl ViewportBounds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||||
pub struct ScrollDelta {
|
pub struct ScrollDelta {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
@@ -47,7 +48,7 @@ impl ScrollDelta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct MouseState {
|
pub struct MouseState {
|
||||||
pub position: ViewportPosition,
|
pub position: ViewportPosition,
|
||||||
pub mouse_keys: MouseKeys,
|
pub mouse_keys: MouseKeys,
|
||||||
@@ -77,7 +78,7 @@ impl MouseState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct EditorMouseState {
|
pub struct EditorMouseState {
|
||||||
pub editor_position: EditorPosition,
|
pub editor_position: EditorPosition,
|
||||||
pub mouse_keys: MouseKeys,
|
pub mouse_keys: MouseKeys,
|
||||||
@@ -116,7 +117,7 @@ impl EditorMouseState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Default)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct MouseKeys: u8 {
|
pub struct MouseKeys: u8 {
|
||||||
const LEFT = 0b0000_0001;
|
const LEFT = 0b0000_0001;
|
||||||
|
|||||||
+8
-9
@@ -26,9 +26,6 @@ pub use graphene::LayerId;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use graphene::document::Document as SvgDocument;
|
pub use graphene::document::Document as SvgDocument;
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use frontend::Callback;
|
|
||||||
|
|
||||||
use communication::dispatcher::Dispatcher;
|
use communication::dispatcher::Dispatcher;
|
||||||
// TODO: serialize with serde to save the current editor state
|
// TODO: serialize with serde to save the current editor state
|
||||||
pub struct Editor {
|
pub struct Editor {
|
||||||
@@ -38,14 +35,16 @@ pub struct Editor {
|
|||||||
use message_prelude::*;
|
use message_prelude::*;
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
pub fn new(callback: Callback) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self { dispatcher: Dispatcher::new() }
|
||||||
dispatcher: Dispatcher::new(callback),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<(), EditorError> {
|
pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Result<Vec<FrontendMessage>, EditorError> {
|
||||||
self.dispatcher.handle_message(message)
|
self.dispatcher.handle_message(message).map(|_| {
|
||||||
|
let mut responses = Vec::new();
|
||||||
|
std::mem::swap(&mut responses, &mut self.dispatcher.responses);
|
||||||
|
responses
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ impl EditorTestUtils for Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn mouseup(&mut self, state: EditorMouseState) {
|
fn mouseup(&mut self, state: EditorMouseState) {
|
||||||
self.handle_message(InputPreprocessorMessage::MouseUp(state, ModifierKeys::default())).unwrap()
|
self.handle_message(InputPreprocessorMessage::MouseUp(state, ModifierKeys::default())).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lmb_mousedown(&mut self, x: f64, y: f64) {
|
fn lmb_mousedown(&mut self, x: f64, y: f64) {
|
||||||
@@ -70,7 +70,7 @@ impl EditorTestUtils for Editor {
|
|||||||
editor_position: (x, y).into(),
|
editor_position: (x, y).into(),
|
||||||
mouse_keys: MouseKeys::LEFT,
|
mouse_keys: MouseKeys::LEFT,
|
||||||
scroll_delta: ScrollDelta::default(),
|
scroll_delta: ScrollDelta::default(),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input(&mut self, message: InputPreprocessorMessage) {
|
fn input(&mut self, message: InputPreprocessorMessage) {
|
||||||
@@ -78,7 +78,7 @@ impl EditorTestUtils for Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn select_tool(&mut self, typ: ToolType) {
|
fn select_tool(&mut self, typ: ToolType) {
|
||||||
self.handle_message(Message::Tool(ToolMessage::SelectTool(typ))).unwrap();
|
self.handle_message(Message::Tool(ToolMessage::ActivateTool(typ))).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_primary_color(&mut self, color: Color) {
|
fn select_primary_color(&mut self, color: Color) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::{
|
|||||||
communication::{message::Message, MessageHandler},
|
communication::{message::Message, MessageHandler},
|
||||||
Color,
|
Color,
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -126,7 +127,7 @@ fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub enum ToolType {
|
pub enum ToolType {
|
||||||
Select,
|
Select,
|
||||||
Crop,
|
Crop,
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ use crate::{
|
|||||||
document::DocumentMessageHandler,
|
document::DocumentMessageHandler,
|
||||||
tool::{tool_options::ToolOptions, DocumentToolData, ToolFsmState, ToolType},
|
tool::{tool_options::ToolOptions, DocumentToolData, ToolFsmState, ToolType},
|
||||||
};
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[impl_message(Message, Tool)]
|
#[impl_message(Message, Tool)]
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum ToolMessage {
|
pub enum ToolMessage {
|
||||||
SelectTool(ToolType),
|
ActivateTool(ToolType),
|
||||||
SelectPrimaryColor(Color),
|
SelectPrimaryColor(Color),
|
||||||
SelectSecondaryColor(Color),
|
SelectSecondaryColor(Color),
|
||||||
|
SelectedLayersChanged,
|
||||||
SwapColors,
|
SwapColors,
|
||||||
ResetColors,
|
ResetColors,
|
||||||
NoOp,
|
NoOp,
|
||||||
@@ -63,7 +65,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
|
|
||||||
update_working_colors(document_data, responses);
|
update_working_colors(document_data, responses);
|
||||||
}
|
}
|
||||||
SelectTool(new_tool) => {
|
ActivateTool(new_tool) => {
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
let document_data = &self.tool_state.document_tool_data;
|
let document_data = &self.tool_state.document_tool_data;
|
||||||
let old_tool = tool_data.active_tool_type;
|
let old_tool = tool_data.active_tool_type;
|
||||||
@@ -75,12 +77,13 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
|
|
||||||
// Get the Abort state of a tool's FSM
|
// Get the Abort state of a tool's FSM
|
||||||
let reset_message = |tool| match tool {
|
let reset_message = |tool| match tool {
|
||||||
ToolType::Ellipse => Some(EllipseMessage::Abort.into()),
|
|
||||||
ToolType::Rectangle => Some(RectangleMessage::Abort.into()),
|
|
||||||
ToolType::Shape => Some(ShapeMessage::Abort.into()),
|
|
||||||
ToolType::Line => Some(LineMessage::Abort.into()),
|
|
||||||
ToolType::Pen => Some(PenMessage::Abort.into()),
|
|
||||||
ToolType::Select => Some(SelectMessage::Abort.into()),
|
ToolType::Select => Some(SelectMessage::Abort.into()),
|
||||||
|
ToolType::Path => Some(PathMessage::Abort.into()),
|
||||||
|
ToolType::Pen => Some(PenMessage::Abort.into()),
|
||||||
|
ToolType::Line => Some(LineMessage::Abort.into()),
|
||||||
|
ToolType::Rectangle => Some(RectangleMessage::Abort.into()),
|
||||||
|
ToolType::Ellipse => Some(EllipseMessage::Abort.into()),
|
||||||
|
ToolType::Shape => Some(ShapeMessage::Abort.into()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -100,8 +103,9 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Special cases for specific tools
|
// Special cases for specific tools
|
||||||
if new_tool == ToolType::Select {
|
// TODO: Refactor to avoid doing this here
|
||||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
if new_tool == ToolType::Select || new_tool == ToolType::Path {
|
||||||
|
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the new active tool
|
// Store the new active tool
|
||||||
@@ -112,6 +116,13 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
let tool_options = self.tool_state.document_tool_data.tool_options.get(&new_tool).map(|tool_options| *tool_options);
|
let tool_options = self.tool_state.document_tool_data.tool_options.get(&new_tool).map(|tool_options| *tool_options);
|
||||||
responses.push_back(FrontendMessage::SetActiveTool { tool_name, tool_options }.into());
|
responses.push_back(FrontendMessage::SetActiveTool { tool_name, tool_options }.into());
|
||||||
}
|
}
|
||||||
|
SelectedLayersChanged => {
|
||||||
|
match self.tool_state.tool_data.active_tool_type {
|
||||||
|
ToolType::Select => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
||||||
|
ToolType::Path => responses.push_back(PathMessage::RedrawOverlay.into()),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
SwapColors => {
|
SwapColors => {
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
|
||||||
@@ -146,7 +157,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, SelectTool, SetToolOptions);
|
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool, SetToolOptions);
|
||||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||||
|
|
||||||
list
|
list
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::ToolActionHandlerData;
|
use crate::tool::ToolActionHandlerData;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Crop;
|
pub struct Crop;
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Crop)]
|
#[impl_message(Message, ToolMessage, Crop)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum CropMessage {
|
pub enum CropMessage {
|
||||||
MouseMove,
|
MouseMove,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
|||||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graphene::{layers::style, Operation};
|
use graphene::{layers::style, Operation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::resize::*;
|
use super::resize::*;
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ pub struct Ellipse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Ellipse)]
|
#[impl_message(Message, ToolMessage, Ellipse)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum EllipseMessage {
|
pub enum EllipseMessage {
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ use crate::tool::{ToolActionHandlerData, ToolMessage};
|
|||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene::layers::LayerDataType;
|
use graphene::layers::LayerDataType;
|
||||||
use graphene::Quad;
|
use graphene::Quad;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Eyedropper;
|
pub struct Eyedropper;
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Eyedropper)]
|
#[impl_message(Message, ToolMessage, Eyedropper)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum EyedropperMessage {
|
pub enum EyedropperMessage {
|
||||||
LeftMouseDown,
|
LeftMouseDown,
|
||||||
RightMouseDown,
|
RightMouseDown,
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ use crate::message_prelude::*;
|
|||||||
use crate::tool::ToolActionHandlerData;
|
use crate::tool::ToolActionHandlerData;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene::{Operation, Quad};
|
use graphene::{Operation, Quad};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Fill;
|
pub struct Fill;
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Fill)]
|
#[impl_message(Message, ToolMessage, Fill)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum FillMessage {
|
pub enum FillMessage {
|
||||||
MouseDown,
|
MouseDown,
|
||||||
}
|
}
|
||||||
@@ -21,7 +22,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
|||||||
|
|
||||||
if let Some(path) = data.0.document.intersects_quad_root(quad).last() {
|
if let Some(path) = data.0.document.intersects_quad_root(quad).last() {
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
Operation::FillLayer {
|
Operation::SetLayerFill {
|
||||||
path: path.to_vec(),
|
path: path.to_vec(),
|
||||||
color: data.1.primary_color,
|
color: data.1.primary_color,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, Too
|
|||||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene::{layers::style, Operation};
|
use graphene::{layers::style, Operation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Line {
|
pub struct Line {
|
||||||
@@ -13,7 +14,7 @@ pub struct Line {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Line)]
|
#[impl_message(Message, ToolMessage, Line)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum LineMessage {
|
pub enum LineMessage {
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::ToolActionHandlerData;
|
use crate::tool::ToolActionHandlerData;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Navigate;
|
pub struct Navigate;
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Navigate)]
|
#[impl_message(Message, ToolMessage, Navigate)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum NavigateMessage {
|
pub enum NavigateMessage {
|
||||||
MouseMove,
|
MouseMove,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,306 @@
|
|||||||
|
use crate::consts::COLOR_ACCENT;
|
||||||
|
use crate::consts::VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE;
|
||||||
|
use crate::document::DocumentMessageHandler;
|
||||||
|
use crate::document::VectorManipulatorSegment;
|
||||||
|
use crate::document::VectorManipulatorShape;
|
||||||
|
use crate::input::InputPreprocessor;
|
||||||
use crate::message_prelude::*;
|
use crate::message_prelude::*;
|
||||||
use crate::tool::ToolActionHandlerData;
|
use crate::tool::ToolActionHandlerData;
|
||||||
|
use crate::tool::{DocumentToolData, Fsm};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene::color::Color;
|
||||||
|
use graphene::layers::style;
|
||||||
|
use graphene::layers::style::Fill;
|
||||||
|
use graphene::layers::style::Stroke;
|
||||||
|
use graphene::Operation;
|
||||||
|
use kurbo::BezPath;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Path;
|
pub struct Path {
|
||||||
|
fsm_state: PathToolFsmState,
|
||||||
|
data: PathToolData,
|
||||||
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Path)]
|
#[impl_message(Message, ToolMessage, Path)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum PathMessage {
|
pub enum PathMessage {
|
||||||
MouseMove,
|
RedrawOverlay,
|
||||||
|
Abort,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
self.fsm_state = self.fsm_state.transition(action, data.0, data.1, &mut self.data, data.2, responses);
|
||||||
|
}
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
use PathToolFsmState::*;
|
||||||
|
match self.fsm_state {
|
||||||
|
Ready => actions!(PathMessageDiscriminant;),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
advertise_actions!();
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum PathToolFsmState {
|
||||||
|
Ready,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PathToolFsmState {
|
||||||
|
fn default() -> Self {
|
||||||
|
PathToolFsmState::Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct PathToolData {
|
||||||
|
anchor_marker_pool: Vec<Vec<LayerId>>,
|
||||||
|
handle_marker_pool: Vec<Vec<LayerId>>,
|
||||||
|
anchor_handle_line_pool: Vec<Vec<LayerId>>,
|
||||||
|
shape_outline_pool: Vec<Vec<LayerId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathToolData {}
|
||||||
|
|
||||||
|
impl Fsm for PathToolFsmState {
|
||||||
|
type ToolData = PathToolData;
|
||||||
|
|
||||||
|
fn transition(
|
||||||
|
self,
|
||||||
|
event: ToolMessage,
|
||||||
|
document: &DocumentMessageHandler,
|
||||||
|
_tool_data: &DocumentToolData,
|
||||||
|
data: &mut Self::ToolData,
|
||||||
|
_input: &InputPreprocessor,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) -> Self {
|
||||||
|
if let ToolMessage::Path(event) = event {
|
||||||
|
use PathMessage::*;
|
||||||
|
use PathToolFsmState::*;
|
||||||
|
match (self, event) {
|
||||||
|
(_, RedrawOverlay) => {
|
||||||
|
let (mut anchor_i, mut handle_i, mut line_i, mut shape_i) = (0, 0, 0, 0);
|
||||||
|
|
||||||
|
let shapes_to_draw = document.selected_layers_vector_points();
|
||||||
|
// Grow the overlay pools by the shortfall, if any
|
||||||
|
let (total_anchors, total_handles, total_anchor_handle_lines) = calculate_total_overlays_per_type(&shapes_to_draw);
|
||||||
|
let total_shapes = shapes_to_draw.len();
|
||||||
|
grow_overlay_pool_entries(&mut data.shape_outline_pool, total_shapes, add_shape_outline, responses);
|
||||||
|
grow_overlay_pool_entries(&mut data.anchor_handle_line_pool, total_anchor_handle_lines, add_anchor_handle_line, responses);
|
||||||
|
grow_overlay_pool_entries(&mut data.anchor_marker_pool, total_anchors, add_anchor_marker, responses);
|
||||||
|
grow_overlay_pool_entries(&mut data.handle_marker_pool, total_handles, add_handle_marker, responses);
|
||||||
|
|
||||||
|
// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function
|
||||||
|
const BIAS: f64 = 0.0001;
|
||||||
|
|
||||||
|
// Draw the overlays for each shape
|
||||||
|
for shape_to_draw in &shapes_to_draw {
|
||||||
|
let shape_layer_path = &data.shape_outline_pool[shape_i];
|
||||||
|
|
||||||
|
responses.push_back(
|
||||||
|
Operation::SetShapePathInViewport {
|
||||||
|
path: shape_layer_path.clone(),
|
||||||
|
bez_path: shape_to_draw.path.clone(),
|
||||||
|
transform: shape_to_draw.transform.to_cols_array(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
responses.push_back(
|
||||||
|
Operation::SetLayerVisibility {
|
||||||
|
path: shape_layer_path.clone(),
|
||||||
|
visible: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
shape_i += 1;
|
||||||
|
|
||||||
|
for segment in &shape_to_draw.segments {
|
||||||
|
let (anchors, handles, anchor_handle_lines) = match segment {
|
||||||
|
VectorManipulatorSegment::Line(a1, a2) => (vec![*a1, *a2], vec![], vec![]),
|
||||||
|
VectorManipulatorSegment::Quad(a1, h1, a2) => (vec![*a1, *a2], vec![*h1], vec![(*h1, *a1)]),
|
||||||
|
VectorManipulatorSegment::Cubic(a1, h1, h2, a2) => (vec![*a1, *a2], vec![*h1, *h2], vec![(*h1, *a1), (*h2, *a2)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Draw the line connecting the anchor with handle for cubic and quadratic bezier segments
|
||||||
|
for anchor_handle_line in anchor_handle_lines {
|
||||||
|
let marker = data.anchor_handle_line_pool[line_i].clone();
|
||||||
|
|
||||||
|
let line_vector = anchor_handle_line.0 - anchor_handle_line.1;
|
||||||
|
|
||||||
|
let scale = DVec2::splat(line_vector.length());
|
||||||
|
let angle = -line_vector.angle_between(DVec2::X);
|
||||||
|
let translation = (anchor_handle_line.1 + BIAS).round() + DVec2::splat(0.5);
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||||
|
|
||||||
|
responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into());
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into());
|
||||||
|
|
||||||
|
line_i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the draggable square points on the end of every line segment or bezier curve segment
|
||||||
|
for anchor in anchors {
|
||||||
|
let marker = data.anchor_marker_pool[anchor_i].clone();
|
||||||
|
|
||||||
|
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||||
|
let angle = 0.;
|
||||||
|
let translation = (anchor - (scale / 2.) + BIAS).round();
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||||
|
|
||||||
|
responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into());
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into());
|
||||||
|
|
||||||
|
anchor_i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the draggable handle for cubic and quadratic bezier segments
|
||||||
|
for handle in handles {
|
||||||
|
let marker = data.handle_marker_pool[handle_i].clone();
|
||||||
|
|
||||||
|
let scale = DVec2::splat(VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE);
|
||||||
|
let angle = 0.;
|
||||||
|
let translation = (handle - (scale / 2.) + BIAS).round();
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||||
|
|
||||||
|
responses.push_back(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into());
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: true }.into());
|
||||||
|
|
||||||
|
handle_i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the remaining pooled overlays
|
||||||
|
for i in anchor_i..data.anchor_marker_pool.len() {
|
||||||
|
let marker = data.anchor_marker_pool[i].clone();
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: false }.into());
|
||||||
|
}
|
||||||
|
for i in handle_i..data.handle_marker_pool.len() {
|
||||||
|
let marker = data.handle_marker_pool[i].clone();
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: marker, visible: false }.into());
|
||||||
|
}
|
||||||
|
for i in line_i..data.anchor_handle_line_pool.len() {
|
||||||
|
let line = data.anchor_handle_line_pool[i].clone();
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: line, visible: false }.into());
|
||||||
|
}
|
||||||
|
for i in shape_i..data.shape_outline_pool.len() {
|
||||||
|
let shape_i = data.shape_outline_pool[i].clone();
|
||||||
|
responses.push_back(Operation::SetLayerVisibility { path: shape_i, visible: false }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, Abort) => {
|
||||||
|
// Destory the overlay layer pools
|
||||||
|
while let Some(layer) = data.anchor_marker_pool.pop() {
|
||||||
|
responses.push_back(Operation::DeleteLayer { path: layer }.into());
|
||||||
|
}
|
||||||
|
while let Some(layer) = data.handle_marker_pool.pop() {
|
||||||
|
responses.push_back(Operation::DeleteLayer { path: layer }.into());
|
||||||
|
}
|
||||||
|
while let Some(layer) = data.anchor_handle_line_pool.pop() {
|
||||||
|
responses.push_back(Operation::DeleteLayer { path: layer }.into());
|
||||||
|
}
|
||||||
|
while let Some(layer) = data.shape_outline_pool.pop() {
|
||||||
|
responses.push_back(Operation::DeleteLayer { path: layer }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_total_overlays_per_type(shapes_to_draw: &Vec<VectorManipulatorShape>) -> (usize, usize, usize) {
|
||||||
|
let (mut total_anchors, mut total_handles, mut total_anchor_handle_lines) = (0, 0, 0);
|
||||||
|
|
||||||
|
for shape_to_draw in shapes_to_draw {
|
||||||
|
for segment in &shape_to_draw.segments {
|
||||||
|
let (anchors, handles, anchor_handle_lines) = match segment {
|
||||||
|
VectorManipulatorSegment::Line(_, _) => (2, 0, 0),
|
||||||
|
VectorManipulatorSegment::Quad(_, _, _) => (2, 1, 1),
|
||||||
|
VectorManipulatorSegment::Cubic(_, _, _, _) => (2, 2, 2),
|
||||||
|
};
|
||||||
|
total_anchors += anchors;
|
||||||
|
total_handles += handles;
|
||||||
|
total_anchor_handle_lines += anchor_handle_lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(total_anchors, total_handles, total_anchor_handle_lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn grow_overlay_pool_entries<F>(pool: &mut Vec<Vec<LayerId>>, total: usize, add_overlay_function: F, responses: &mut VecDeque<Message>)
|
||||||
|
where
|
||||||
|
F: Fn(&mut VecDeque<Message>) -> Vec<LayerId>,
|
||||||
|
{
|
||||||
|
if pool.len() < total {
|
||||||
|
let additional = total - pool.len();
|
||||||
|
|
||||||
|
pool.reserve(additional);
|
||||||
|
|
||||||
|
for _ in 0..additional {
|
||||||
|
let marker = add_overlay_function(responses);
|
||||||
|
pool.push(marker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_anchor_marker(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||||
|
let layer_path = vec![generate_uuid()];
|
||||||
|
responses.push_back(
|
||||||
|
Operation::AddOverlayRect {
|
||||||
|
path: layer_path.clone(),
|
||||||
|
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||||
|
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
layer_path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_handle_marker(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||||
|
let layer_path = vec![generate_uuid()];
|
||||||
|
responses.push_back(
|
||||||
|
Operation::AddOverlayEllipse {
|
||||||
|
path: layer_path.clone(),
|
||||||
|
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||||
|
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
layer_path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_anchor_handle_line(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||||
|
let layer_path = vec![generate_uuid()];
|
||||||
|
responses.push_back(
|
||||||
|
Operation::AddOverlayLine {
|
||||||
|
path: layer_path.clone(),
|
||||||
|
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||||
|
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
layer_path
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_shape_outline(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||||
|
let layer_path = vec![generate_uuid()];
|
||||||
|
responses.push_back(
|
||||||
|
Operation::AddOverlayShape {
|
||||||
|
path: layer_path.clone(),
|
||||||
|
bez_path: BezPath::default(),
|
||||||
|
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
|
layer_path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, Too
|
|||||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graphene::{layers::style, Operation};
|
use graphene::{layers::style, Operation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Pen {
|
pub struct Pen {
|
||||||
@@ -11,7 +12,7 @@ pub struct Pen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Pen)]
|
#[impl_message(Message, ToolMessage, Pen)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum PenMessage {
|
pub enum PenMessage {
|
||||||
Undo,
|
Undo,
|
||||||
DragStart,
|
DragStart,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
|||||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graphene::{layers::style, Operation};
|
use graphene::{layers::style, Operation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::resize::*;
|
use super::resize::*;
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ pub struct Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Rectangle)]
|
#[impl_message(Message, ToolMessage, Rectangle)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum RectangleMessage {
|
pub enum RectangleMessage {
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use graphene::color::Color;
|
|
||||||
use graphene::layers::style;
|
use graphene::layers::style;
|
||||||
use graphene::layers::style::Fill;
|
use graphene::layers::style::Fill;
|
||||||
use graphene::layers::style::Stroke;
|
use graphene::layers::style::Stroke;
|
||||||
@@ -8,6 +7,7 @@ use graphene::Quad;
|
|||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::consts::COLOR_ACCENT;
|
||||||
use crate::input::keyboard::Key;
|
use crate::input::keyboard::Key;
|
||||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||||
@@ -24,7 +24,7 @@ pub struct Select {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Select)]
|
#[impl_message(Message, ToolMessage, Select)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum SelectMessage {
|
pub enum SelectMessage {
|
||||||
DragStart { add_to_selection: Key },
|
DragStart { add_to_selection: Key },
|
||||||
DragStop,
|
DragStop,
|
||||||
@@ -70,7 +70,7 @@ struct SelectToolData {
|
|||||||
drag_current: ViewportPosition,
|
drag_current: ViewportPosition,
|
||||||
layers_dragging: Vec<Vec<LayerId>>, // Paths and offsets
|
layers_dragging: Vec<Vec<LayerId>>, // Paths and offsets
|
||||||
drag_box_id: Option<Vec<LayerId>>,
|
drag_box_id: Option<Vec<LayerId>>,
|
||||||
bounding_box_id: Option<Vec<LayerId>>,
|
bounding_box_path: Option<Vec<LayerId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectToolData {
|
impl SelectToolData {
|
||||||
@@ -89,13 +89,13 @@ impl SelectToolData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_boundnig_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
fn add_bounding_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
||||||
let path = vec![generate_uuid()];
|
let path = vec![generate_uuid()];
|
||||||
responses.push(
|
responses.push(
|
||||||
Operation::AddBoundingBox {
|
Operation::AddOverlayRect {
|
||||||
path: path.clone(),
|
path: path.clone(),
|
||||||
transform: DAffine2::ZERO.to_cols_array(),
|
transform: DAffine2::ZERO.to_cols_array(),
|
||||||
style: style::PathStyle::new(Some(Stroke::new(Color::from_rgb8(0x00, 0xA8, 0xFF), 1.0)), Some(Fill::none())),
|
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
@@ -125,11 +125,11 @@ impl Fsm for SelectToolFsmState {
|
|||||||
match (self, event) {
|
match (self, event) {
|
||||||
(_, UpdateSelectionBoundingBox) => {
|
(_, UpdateSelectionBoundingBox) => {
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
let response = match (document.selected_layers_bounding_box(), data.bounding_box_id.take()) {
|
let response = match (document.selected_layers_bounding_box(), data.bounding_box_path.take()) {
|
||||||
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
|
(None, Some(path)) => Operation::DeleteLayer { path }.into(),
|
||||||
(Some([pos1, pos2]), path) => {
|
(Some([pos1, pos2]), path) => {
|
||||||
let path = path.unwrap_or_else(|| add_boundnig_box(&mut buffer));
|
let path = path.unwrap_or_else(|| add_bounding_box(&mut buffer));
|
||||||
data.bounding_box_id = Some(path.clone());
|
data.bounding_box_path = Some(path.clone());
|
||||||
let transform = transform_from_box(pos1, pos2);
|
let transform = transform_from_box(pos1, pos2);
|
||||||
Operation::SetLayerTransformInViewport { path, transform }.into()
|
Operation::SetLayerTransformInViewport { path, transform }.into()
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
if !input.keyboard.get(add_to_selection as usize) {
|
if !input.keyboard.get(add_to_selection as usize) {
|
||||||
buffer.push(DocumentMessage::DeselectAllLayers.into());
|
buffer.push(DocumentMessage::DeselectAllLayers.into());
|
||||||
}
|
}
|
||||||
data.drag_box_id = Some(add_boundnig_box(&mut buffer));
|
data.drag_box_id = Some(add_bounding_box(&mut buffer));
|
||||||
DrawingBox
|
DrawingBox
|
||||||
};
|
};
|
||||||
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
||||||
@@ -220,7 +220,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
(_, Abort) => {
|
(_, Abort) => {
|
||||||
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_front(Operation::DeleteLayer { path }.into()));
|
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_front(Operation::DeleteLayer { path }.into()));
|
||||||
delete(&mut data.drag_box_id);
|
delete(&mut data.drag_box_id);
|
||||||
delete(&mut data.bounding_box_id);
|
delete(&mut data.bounding_box_path);
|
||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
(_, Align(axis, aggregate)) => {
|
(_, Align(axis, aggregate)) => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::tool::{DocumentToolData, Fsm, ShapeType, ToolActionHandlerData, ToolO
|
|||||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||||
use glam::DAffine2;
|
use glam::DAffine2;
|
||||||
use graphene::{layers::style, Operation};
|
use graphene::{layers::style, Operation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::resize::*;
|
use super::resize::*;
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ pub struct Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Shape)]
|
#[impl_message(Message, ToolMessage, Shape)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum ShapeMessage {
|
pub enum ShapeMessage {
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
@@ -82,7 +83,7 @@ impl Fsm for ShapeToolFsmState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
Operation::AddShape {
|
Operation::AddNgon {
|
||||||
path: shape_data.path.clone().unwrap(),
|
path: shape_data.path.clone().unwrap(),
|
||||||
insert_index: -1,
|
insert_index: -1,
|
||||||
transform: DAffine2::ZERO.to_cols_array(),
|
transform: DAffine2::ZERO.to_cols_array(),
|
||||||
|
|||||||
+1
-2
@@ -1,4 +1,3 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
pkg/
|
wasm/pkg/
|
||||||
wasm/pkg/*
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
|
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
||||||
|
|
||||||
<ShelfItemInput icon="VectorPathTool" title="Path Tool (A)" :active="activeTool === 'Path'" :action="() => comingSoon(82) && selectTool('Path')" />
|
<ShelfItemInput icon="VectorPathTool" title="Path Tool (A)" :active="activeTool === 'Path'" :action="() => selectTool('Path')" />
|
||||||
<ShelfItemInput icon="VectorPenTool" title="Pen Tool (P)" :active="activeTool === 'Pen'" :action="() => selectTool('Pen')" />
|
<ShelfItemInput icon="VectorPenTool" title="Pen Tool (P)" :active="activeTool === 'Pen'" :action="() => selectTool('Pen')" />
|
||||||
<ShelfItemInput icon="VectorFreehandTool" title="Freehand Tool (N)" :active="activeTool === 'Freehand'" :action="() => comingSoon() && selectTool('Freehand')" />
|
<ShelfItemInput icon="VectorFreehandTool" title="Freehand Tool (N)" :active="activeTool === 'Freehand'" :action="() => comingSoon() && selectTool('Freehand')" />
|
||||||
<ShelfItemInput icon="VectorSplineTool" title="Spline Tool" :active="activeTool === 'Spline'" :action="() => comingSoon() && selectTool('Spline')" />
|
<ShelfItemInput icon="VectorSplineTool" title="Spline Tool" :active="activeTool === 'Spline'" :action="() => comingSoon() && selectTool('Spline')" />
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
pkg/
|
|
||||||
@@ -23,6 +23,9 @@ log = "0.4"
|
|||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"] }
|
wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 3
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.22"
|
wasm-bindgen-test = "0.3.22"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::shims::Error;
|
use crate::shims::Error;
|
||||||
use crate::wrappers::{translate_key, translate_tool, Color};
|
use crate::wrappers::{translate_key, translate_tool, Color};
|
||||||
use crate::EDITOR_STATE;
|
|
||||||
use editor::input::input_preprocessor::ModifierKeys;
|
use editor::input::input_preprocessor::ModifierKeys;
|
||||||
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||||
use editor::message_prelude::*;
|
use editor::message_prelude::*;
|
||||||
@@ -14,23 +13,31 @@ fn convert_error(err: editor::EditorError) -> JsValue {
|
|||||||
Error::new(&err.to_string()).into()
|
Error::new(&err.to_string()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dispatch<T: Into<Message>>(message: T) -> Result<(), JsValue> {
|
||||||
|
let result = crate::EDITOR_STATE.with(|state| state.borrow_mut().handle_message(message.into()));
|
||||||
|
if let Ok(messages) = result {
|
||||||
|
crate::handle_responses(messages);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Modify the currently selected tool in the document state store
|
/// Modify the currently selected tool in the document state store
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_tool(tool: String) -> Result<(), JsValue> {
|
pub fn select_tool(tool: String) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| match translate_tool(&tool) {
|
match translate_tool(&tool) {
|
||||||
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SelectTool(tool)).map_err(convert_error),
|
Some(tool) => dispatch(ToolMessage::ActivateTool(tool)),
|
||||||
None => Err(Error::new(&format!("Couldn't select {} because it was not recognized as a valid tool", tool)).into()),
|
None => Err(Error::new(&format!("Couldn't select {} because it was not recognized as a valid tool", tool)).into()),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the options for a given tool
|
/// Update the options for a given tool
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_tool_options(tool: String, options: &JsValue) -> Result<(), JsValue> {
|
pub fn set_tool_options(tool: String, options: &JsValue) -> Result<(), JsValue> {
|
||||||
match options.into_serde::<ToolOptions>() {
|
match options.into_serde::<ToolOptions>() {
|
||||||
Ok(options) => EDITOR_STATE.with(|editor| match translate_tool(&tool) {
|
Ok(options) => match translate_tool(&tool) {
|
||||||
Some(tool) => editor.borrow_mut().handle_message(ToolMessage::SetToolOptions(tool, options)).map_err(convert_error),
|
Some(tool) => dispatch(ToolMessage::SetToolOptions(tool, options)),
|
||||||
None => Err(Error::new(&format!("Couldn't set options for {} because it was not recognized as a valid tool", tool)).into()),
|
None => Err(Error::new(&format!("Couldn't set options for {} because it was not recognized as a valid tool", tool)).into()),
|
||||||
}),
|
},
|
||||||
Err(err) => Err(Error::new(&format!("Invalid JSON for ToolOptions: {}", err)).into()),
|
Err(err) => Err(Error::new(&format!("Invalid JSON for ToolOptions: {}", err)).into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,60 +55,60 @@ pub fn send_tool_message(tool: String, message: &JsValue) -> Result<(), JsValue>
|
|||||||
},
|
},
|
||||||
None => Err(Error::new(&format!("Couldn't send message for {} because it was not recognized as a valid tool", tool)).into()),
|
None => Err(Error::new(&format!("Couldn't send message for {} because it was not recognized as a valid tool", tool)).into()),
|
||||||
};
|
};
|
||||||
EDITOR_STATE.with(|editor| match tool_message {
|
match tool_message {
|
||||||
Ok(tool_message) => editor.borrow_mut().handle_message(tool_message).map_err(convert_error),
|
Ok(tool_message) => dispatch(tool_message),
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_document(document: usize) -> Result<(), JsValue> {
|
pub fn select_document(document: usize) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::SelectDocument(document)).map_err(convert_error))
|
dispatch(DocumentsMessage::SelectDocument(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn get_open_documents_list() -> Result<(), JsValue> {
|
pub fn get_open_documents_list() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::GetOpenDocumentsList).map_err(convert_error))
|
dispatch(DocumentsMessage::GetOpenDocumentsList)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn new_document() -> Result<(), JsValue> {
|
pub fn new_document() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::NewDocument).map_err(convert_error))
|
dispatch(DocumentsMessage::NewDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn open_document() -> Result<(), JsValue> {
|
pub fn open_document() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::OpenDocument).map_err(convert_error))
|
dispatch(DocumentsMessage::OpenDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn open_document_file(name: String, content: String) -> Result<(), JsValue> {
|
pub fn open_document_file(name: String, content: String) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::OpenDocumentFile(name, content)).map_err(convert_error))
|
dispatch(DocumentsMessage::OpenDocumentFile(name, content))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn save_document() -> Result<(), JsValue> {
|
pub fn save_document() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SaveDocument)).map_err(convert_error)
|
dispatch(DocumentMessage::SaveDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn close_document(document: usize) -> Result<(), JsValue> {
|
pub fn close_document(document: usize) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseDocument(document)).map_err(convert_error))
|
dispatch(DocumentsMessage::CloseDocument(document))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn close_all_documents() -> Result<(), JsValue> {
|
pub fn close_all_documents() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseAllDocuments).map_err(convert_error))
|
dispatch(DocumentsMessage::CloseAllDocuments)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn close_active_document_with_confirmation() -> Result<(), JsValue> {
|
pub fn close_active_document_with_confirmation() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseActiveDocumentWithConfirmation).map_err(convert_error))
|
dispatch(DocumentsMessage::CloseActiveDocumentWithConfirmation)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn close_all_documents_with_confirmation() -> Result<(), JsValue> {
|
pub fn close_all_documents_with_confirmation() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentsMessage::CloseAllDocumentsWithConfirmation).map_err(convert_error))
|
dispatch(DocumentsMessage::CloseAllDocumentsWithConfirmation)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send new bounds when document panel viewports get resized or moved within the editor
|
/// Send new bounds when document panel viewports get resized or moved within the editor
|
||||||
@@ -110,7 +117,7 @@ pub fn close_all_documents_with_confirmation() -> Result<(), JsValue> {
|
|||||||
pub fn bounds_of_viewports(bounds_of_viewports: &[f64]) -> Result<(), JsValue> {
|
pub fn bounds_of_viewports(bounds_of_viewports: &[f64]) -> Result<(), JsValue> {
|
||||||
let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect();
|
let chunked: Vec<_> = bounds_of_viewports.chunks(4).map(ViewportBounds::from_slice).collect();
|
||||||
let ev = InputPreprocessorMessage::BoundsOfViewports(chunked);
|
let ev = InputPreprocessorMessage::BoundsOfViewports(chunked);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mouse movement within the screenspace bounds of the viewport
|
/// Mouse movement within the screenspace bounds of the viewport
|
||||||
@@ -121,7 +128,7 @@ pub fn on_mouse_move(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Result<()
|
|||||||
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
|
|
||||||
let ev = InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys);
|
let ev = InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mouse scrolling within the screenspace bounds of the viewport
|
/// Mouse scrolling within the screenspace bounds of the viewport
|
||||||
@@ -133,7 +140,7 @@ pub fn on_mouse_scroll(x: f64, y: f64, mouse_keys: u8, wheel_delta_x: i32, wheel
|
|||||||
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
|
|
||||||
let ev = InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys);
|
let ev = InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mouse button depressed within screenspace the bounds of the viewport
|
/// A mouse button depressed within screenspace the bounds of the viewport
|
||||||
@@ -144,7 +151,7 @@ pub fn on_mouse_down(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Result<()
|
|||||||
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
|
|
||||||
let ev = InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys);
|
let ev = InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mouse button released
|
/// A mouse button released
|
||||||
@@ -155,7 +162,7 @@ pub fn on_mouse_up(x: f64, y: f64, mouse_keys: u8, modifiers: u8) -> Result<(),
|
|||||||
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let modifier_keys = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
|
|
||||||
let ev = InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys);
|
let ev = InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A keyboard button depressed within screenspace the bounds of the viewport
|
/// A keyboard button depressed within screenspace the bounds of the viewport
|
||||||
@@ -165,7 +172,7 @@ pub fn on_key_down(name: String, modifiers: u8) -> Result<(), JsValue> {
|
|||||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
log::trace!("key down {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
log::trace!("key down {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
||||||
let ev = InputPreprocessorMessage::KeyDown(key, mods);
|
let ev = InputPreprocessorMessage::KeyDown(key, mods);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A keyboard button released
|
/// A keyboard button released
|
||||||
@@ -175,69 +182,61 @@ pub fn on_key_up(name: String, modifiers: u8) -> Result<(), JsValue> {
|
|||||||
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
let mods = ModifierKeys::from_bits(modifiers).expect("invalid modifier keys");
|
||||||
log::trace!("key up {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
log::trace!("key up {:?}, name: {}, modifiers: {:?}", key, name, mods);
|
||||||
let ev = InputPreprocessorMessage::KeyUp(key, mods);
|
let ev = InputPreprocessorMessage::KeyUp(key, mods);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update primary color
|
/// Update primary color
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn update_primary_color(primary_color: Color) -> Result<(), JsValue> {
|
pub fn update_primary_color(primary_color: Color) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(ToolMessage::SelectPrimaryColor(primary_color.inner()))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SelectPrimaryColor(primary_color.inner())))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update secondary color
|
/// Update secondary color
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn update_secondary_color(secondary_color: Color) -> Result<(), JsValue> {
|
pub fn update_secondary_color(secondary_color: Color) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(ToolMessage::SelectSecondaryColor(secondary_color.inner()))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SelectSecondaryColor(secondary_color.inner())))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Swap primary and secondary color
|
/// Swap primary and secondary color
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn swap_colors() -> Result<(), JsValue> {
|
pub fn swap_colors() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ToolMessage::SwapColors)).map_err(convert_error)
|
dispatch(ToolMessage::SwapColors)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset primary and secondary colors to their defaults
|
/// Reset primary and secondary colors to their defaults
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn reset_colors() -> Result<(), JsValue> {
|
pub fn reset_colors() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ToolMessage::ResetColors)).map_err(convert_error)
|
dispatch(ToolMessage::ResetColors)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undo history one step
|
/// Undo history one step
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn undo() -> Result<(), JsValue> {
|
pub fn undo() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Undo)).map_err(convert_error)
|
dispatch(DocumentMessage::Undo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Redo history one step
|
/// Redo history one step
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn redo() -> Result<(), JsValue> {
|
pub fn redo() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::Redo)).map_err(convert_error)
|
dispatch(DocumentMessage::Redo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select all layers
|
/// Select all layers
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_all_layers() -> Result<(), JsValue> {
|
pub fn select_all_layers() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SelectAllLayers)).map_err(convert_error)
|
dispatch(DocumentMessage::SelectAllLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deselect all layers
|
/// Deselect all layers
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn deselect_all_layers() -> Result<(), JsValue> {
|
pub fn deselect_all_layers() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::DeselectAllLayers)
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::DeselectAllLayers))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reorder selected layer
|
/// Reorder selected layer
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn reorder_selected_layers(delta: i32) -> Result<(), JsValue> {
|
pub fn reorder_selected_layers(delta: i32) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::ReorderSelectedLayers(delta))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ReorderSelectedLayers(delta)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the blend mode for the selected layers
|
/// Set the blend mode for the selected layers
|
||||||
@@ -263,111 +262,96 @@ pub fn set_blend_mode_for_selected_layers(blend_mode_svg_style_name: String) ->
|
|||||||
_ => return Err(convert_error(EditorError::Misc("UnknownBlendMode".to_string()))),
|
_ => return Err(convert_error(EditorError::Misc("UnknownBlendMode".to_string()))),
|
||||||
};
|
};
|
||||||
|
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SetBlendModeForSelectedLayers(blend_mode)).map_err(convert_error))
|
dispatch(DocumentMessage::SetBlendModeForSelectedLayers(blend_mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the opacity for the selected layers
|
/// Set the opacity for the selected layers
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_opacity_for_selected_layers(opacity_percent: f64) -> Result<(), JsValue> {
|
pub fn set_opacity_for_selected_layers(opacity_percent: f64) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| {
|
dispatch(DocumentMessage::SetOpacityForSelectedLayers(opacity_percent / 100.))
|
||||||
editor
|
|
||||||
.borrow_mut()
|
|
||||||
.handle_message(DocumentMessage::SetOpacityForSelectedLayers(opacity_percent / 100.))
|
|
||||||
.map_err(convert_error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export the document
|
/// Export the document
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn export_document() -> Result<(), JsValue> {
|
pub fn export_document() -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ExportDocument)).map_err(convert_error)
|
dispatch(DocumentMessage::ExportDocument)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the zoom to the value
|
/// Sets the zoom to the value
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_canvas_zoom(new_zoom: f64) -> Result<(), JsValue> {
|
pub fn set_canvas_zoom(new_zoom: f64) -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::SetCanvasZoom(new_zoom);
|
let ev = MovementMessage::SetCanvasZoom(new_zoom);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zoom in to the next step
|
/// Zoom in to the next step
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn increase_canvas_zoom() -> Result<(), JsValue> {
|
pub fn increase_canvas_zoom() -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::IncreaseCanvasZoom;
|
let ev = MovementMessage::IncreaseCanvasZoom;
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Zoom out to the next step
|
/// Zoom out to the next step
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn decrease_canvas_zoom() -> Result<(), JsValue> {
|
pub fn decrease_canvas_zoom() -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::DecreaseCanvasZoom;
|
let ev = MovementMessage::DecreaseCanvasZoom;
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the rotation to the new value (in radians)
|
/// Sets the rotation to the new value (in radians)
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn set_rotation(new_radians: f64) -> Result<(), JsValue> {
|
pub fn set_rotation(new_radians: f64) -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::SetCanvasRotation(new_radians);
|
let ev = MovementMessage::SetCanvasRotation(new_radians);
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates document (in viewport coords)
|
/// Translates document (in viewport coords)
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn translate_canvas(delta_x: f64, delta_y: f64) -> Result<(), JsValue> {
|
pub fn translate_canvas(delta_x: f64, delta_y: f64) -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::TranslateCanvas((delta_x, delta_y).into());
|
let ev = MovementMessage::TranslateCanvas((delta_x, delta_y).into());
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translates document (in viewport coords)
|
/// Translates document (in viewport coords)
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn translate_canvas_by_fraction(delta_x: f64, delta_y: f64) -> Result<(), JsValue> {
|
pub fn translate_canvas_by_fraction(delta_x: f64, delta_y: f64) -> Result<(), JsValue> {
|
||||||
let ev = MovementMessage::TranslateCanvasByViewportFraction((delta_x, delta_y).into());
|
let ev = MovementMessage::TranslateCanvasByViewportFraction((delta_x, delta_y).into());
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(ev)).map_err(convert_error)
|
dispatch(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX
|
/// Update the list of selected layers. The layer paths have to be stored in one array and are separated by LayerId::MAX
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn select_layers(paths: Vec<LayerId>) -> Result<(), JsValue> {
|
pub fn select_layers(paths: Vec<LayerId>) -> Result<(), JsValue> {
|
||||||
let paths = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect();
|
let paths = paths.split(|id| *id == LayerId::MAX).map(|path| path.to_vec()).collect();
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::SetSelectedLayers(paths))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::SetSelectedLayers(paths)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle visibility of a layer from the layer list
|
/// Toggle visibility of a layer from the layer list
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn toggle_layer_visibility(path: Vec<LayerId>) -> Result<(), JsValue> {
|
pub fn toggle_layer_visibility(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::ToggleLayerVisibility(path))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ToggleLayerVisibility(path)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggle expansions state of a layer from the layer list
|
/// Toggle expansions state of a layer from the layer list
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn toggle_layer_expansion(path: Vec<LayerId>) -> Result<(), JsValue> {
|
pub fn toggle_layer_expansion(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::ToggleLayerExpansion(path))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::ToggleLayerExpansion(path)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renames a layer from the layer list
|
/// Renames a layer from the layer list
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn rename_layer(path: Vec<LayerId>, new_name: String) -> Result<(), JsValue> {
|
pub fn rename_layer(path: Vec<LayerId>, new_name: String) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::RenameLayer(path, new_name))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::RenameLayer(path, new_name)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a layer from the layer list
|
/// Deletes a layer from the layer list
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn delete_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
|
pub fn delete_layer(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE
|
dispatch(DocumentMessage::DeleteLayer(path))
|
||||||
.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::DeleteLayer(path)))
|
|
||||||
.map_err(convert_error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests the backend to add a layer to the layer list
|
/// Requests the backend to add a layer to the layer list
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn add_folder(path: Vec<LayerId>) -> Result<(), JsValue> {
|
pub fn add_folder(path: Vec<LayerId>) -> Result<(), JsValue> {
|
||||||
EDITOR_STATE.with(|editor| editor.borrow_mut().handle_message(DocumentMessage::AddFolder(path))).map_err(convert_error)
|
dispatch(DocumentMessage::AddFolder(path))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ use utils::WasmLog;
|
|||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
// the thread_local macro provides a way to initialize static variables with non-constant functions
|
// the thread_local macro provides a way to initialize static variables with non-constant functions
|
||||||
thread_local! { pub static EDITOR_STATE: RefCell<Editor> = RefCell::new(Editor::new(Box::new(handle_response))) }
|
thread_local! {
|
||||||
|
pub static EDITOR_STATE: RefCell<Editor> = RefCell::new(Editor::new());
|
||||||
|
}
|
||||||
static LOGGER: WasmLog = WasmLog;
|
static LOGGER: WasmLog = WasmLog;
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
#[wasm_bindgen(start)]
|
||||||
@@ -20,6 +22,12 @@ pub fn init() {
|
|||||||
log::set_max_level(log::LevelFilter::Debug);
|
log::set_max_level(log::LevelFilter::Debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_responses(responses: Vec<FrontendMessage>) {
|
||||||
|
for response in responses.into_iter() {
|
||||||
|
handle_response(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(module = "/../src/utilities/response-handler-binding.ts")]
|
#[wasm_bindgen(module = "/../src/utilities/response-handler-binding.ts")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[wasm_bindgen(catch)]
|
#[wasm_bindgen(catch)]
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ impl Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return an opaque `Color` from given `f32` RGB channels.
|
/// Return an opaque `Color` from given `f32` RGB channels.
|
||||||
const fn from_unsafe(red: f32, green: f32, blue: f32) -> Color {
|
pub const fn from_unsafe(red: f32, green: f32, blue: f32) -> Color {
|
||||||
Color { red, green, blue, alpha: 1. }
|
Color { red, green, blue, alpha: 1. }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+89
-13
@@ -215,7 +215,6 @@ impl Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
|
pub fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
|
||||||
self.mark_downstream_as_dirty(path)?;
|
|
||||||
self.mark_upstream_as_dirty(path)?;
|
self.mark_upstream_as_dirty(path)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -264,6 +263,10 @@ impl Document {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_transform_relative_to_viewport(&self, from: &[LayerId]) -> Result<DAffine2, DocumentError> {
|
||||||
|
self.generate_transform_across_scope(from, None)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn apply_transform_relative_to_viewport(&mut self, layer: &[LayerId], transform: DAffine2) -> Result<(), DocumentError> {
|
pub fn apply_transform_relative_to_viewport(&mut self, layer: &[LayerId], transform: DAffine2) -> Result<(), DocumentError> {
|
||||||
self.transform_relative_to_scope(layer, None, transform)
|
self.transform_relative_to_scope(layer, None, transform)
|
||||||
}
|
}
|
||||||
@@ -297,33 +300,79 @@ impl Document {
|
|||||||
|
|
||||||
let responses = match &operation {
|
let responses = match &operation {
|
||||||
Operation::AddEllipse { path, insert_index, transform, style } => {
|
Operation::AddEllipse { path, insert_index, transform, style } => {
|
||||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::ellipse(*style)), *transform), *insert_index)?;
|
let layer = Layer::new(LayerDataType::Shape(Shape::ellipse(*style)), *transform);
|
||||||
|
|
||||||
|
self.set_layer(path, layer, *insert_index)?;
|
||||||
|
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
|
}
|
||||||
|
Operation::AddOverlayEllipse { path, transform, style } => {
|
||||||
|
let mut ellipse = Shape::ellipse(*style);
|
||||||
|
ellipse.render_index = -1;
|
||||||
|
|
||||||
|
let mut layer = Layer::new(LayerDataType::Shape(ellipse), *transform);
|
||||||
|
layer.overlay = true;
|
||||||
|
|
||||||
|
self.set_layer(path, layer, -1)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::AddRect { path, insert_index, transform, style } => {
|
Operation::AddRect { path, insert_index, transform, style } => {
|
||||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::rectangle(*style)), *transform), *insert_index)?;
|
let layer = Layer::new(LayerDataType::Shape(Shape::rectangle(*style)), *transform);
|
||||||
|
|
||||||
|
self.set_layer(path, layer, *insert_index)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::AddBoundingBox { path, transform, style } => {
|
Operation::AddOverlayRect { path, transform, style } => {
|
||||||
let mut rect = Shape::rectangle(*style);
|
let mut rect = Shape::rectangle(*style);
|
||||||
rect.render_index = -1;
|
rect.render_index = -1;
|
||||||
|
|
||||||
let mut layer = Layer::new(LayerDataType::Shape(rect), *transform);
|
let mut layer = Layer::new(LayerDataType::Shape(rect), *transform);
|
||||||
layer.overlay = true;
|
layer.overlay = true;
|
||||||
|
|
||||||
self.set_layer(path, layer, -1)?;
|
self.set_layer(path, layer, -1)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::AddShape {
|
Operation::AddLine { path, insert_index, transform, style } => {
|
||||||
|
let layer = Layer::new(LayerDataType::Shape(Shape::line(*style)), *transform);
|
||||||
|
|
||||||
|
self.set_layer(path, layer, *insert_index)?;
|
||||||
|
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
|
}
|
||||||
|
Operation::AddOverlayLine { path, transform, style } => {
|
||||||
|
let mut line = Shape::line(*style);
|
||||||
|
line.render_index = -1;
|
||||||
|
|
||||||
|
let mut layer = Layer::new(LayerDataType::Shape(line), *transform);
|
||||||
|
layer.overlay = true;
|
||||||
|
|
||||||
|
self.set_layer(path, layer, -1)?;
|
||||||
|
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
|
}
|
||||||
|
Operation::AddNgon {
|
||||||
path,
|
path,
|
||||||
insert_index,
|
insert_index,
|
||||||
transform,
|
transform,
|
||||||
style,
|
style,
|
||||||
sides,
|
sides,
|
||||||
} => {
|
} => {
|
||||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::shape(*sides, *style)), *transform), *insert_index)?;
|
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::ngon(*sides, *style)), *transform), *insert_index)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::AddLine { path, insert_index, transform, style } => {
|
Operation::AddOverlayShape { path, style, bez_path } => {
|
||||||
self.set_layer(path, Layer::new(LayerDataType::Shape(Shape::line(*style)), *transform), *insert_index)?;
|
let mut shape = Shape::from_bez_path(bez_path.clone(), *style, false);
|
||||||
|
shape.render_index = -1;
|
||||||
|
|
||||||
|
let mut layer = Layer::new(LayerDataType::Shape(shape), DAffine2::IDENTITY.to_cols_array());
|
||||||
|
layer.overlay = true;
|
||||||
|
|
||||||
|
self.set_layer(path, layer, -1)?;
|
||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::CreatedLayer { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::AddPen {
|
Operation::AddPen {
|
||||||
@@ -395,6 +444,19 @@ impl Document {
|
|||||||
self.mark_as_dirty(path)?;
|
self.mark_as_dirty(path)?;
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
}
|
}
|
||||||
|
Operation::SetShapePathInViewport { path, bez_path, transform } => {
|
||||||
|
let transform = DAffine2::from_cols_array(transform);
|
||||||
|
self.set_transform_relative_to_viewport(path, transform)?;
|
||||||
|
self.mark_as_dirty(path)?;
|
||||||
|
|
||||||
|
match &mut self.layer_mut(path)?.data {
|
||||||
|
LayerDataType::Shape(shape) => {
|
||||||
|
shape.path = bez_path.clone();
|
||||||
|
}
|
||||||
|
LayerDataType::Folder(_) => (),
|
||||||
|
}
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
|
}
|
||||||
Operation::TransformLayerInScope { path, transform, scope } => {
|
Operation::TransformLayerInScope { path, transform, scope } => {
|
||||||
let transform = DAffine2::from_cols_array(transform);
|
let transform = DAffine2::from_cols_array(transform);
|
||||||
let scope = DAffine2::from_cols_array(scope);
|
let scope = DAffine2::from_cols_array(scope);
|
||||||
@@ -416,11 +478,16 @@ impl Document {
|
|||||||
self.mark_as_dirty(path)?;
|
self.mark_as_dirty(path)?;
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::ToggleVisibility { path } => {
|
Operation::ToggleLayerVisibility { path } => {
|
||||||
self.mark_as_dirty(path)?;
|
self.mark_as_dirty(path)?;
|
||||||
if let Ok(layer) = self.layer_mut(path) {
|
let layer = self.layer_mut(path)?;
|
||||||
layer.visible = !layer.visible;
|
layer.visible = !layer.visible;
|
||||||
}
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
|
}
|
||||||
|
Operation::SetLayerVisibility { path, visible } => {
|
||||||
|
self.mark_as_dirty(path)?;
|
||||||
|
let layer = self.layer_mut(path)?;
|
||||||
|
layer.visible = *visible;
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::SetLayerBlendMode { path, blend_mode } => {
|
Operation::SetLayerBlendMode { path, blend_mode } => {
|
||||||
@@ -435,7 +502,16 @@ impl Document {
|
|||||||
|
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
}
|
}
|
||||||
Operation::FillLayer { path, color } => {
|
Operation::SetLayerStyle { path, style } => {
|
||||||
|
let layer = self.layer_mut(path)?;
|
||||||
|
match &mut layer.data {
|
||||||
|
LayerDataType::Shape(s) => s.style = *style,
|
||||||
|
_ => return Err(DocumentError::NotAShape),
|
||||||
|
}
|
||||||
|
self.mark_as_dirty(path)?;
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::LayerChanged { path: path.clone() }])
|
||||||
|
}
|
||||||
|
Operation::SetLayerFill { path, color } => {
|
||||||
let layer = self.layer_mut(path)?;
|
let layer = self.layer_mut(path)?;
|
||||||
match &mut layer.data {
|
match &mut layer.data {
|
||||||
LayerDataType::Shape(s) => s.style.set_fill(layers::style::Fill::new(*color)),
|
LayerDataType::Shape(s) => s.style.set_fill(layers::style::Fill::new(*color)),
|
||||||
|
|||||||
@@ -77,7 +77,16 @@ impl Shape {
|
|||||||
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
|
transforms.iter().skip(start).cloned().reduce(|a, b| a * b).unwrap_or(DAffine2::IDENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shape(sides: u8, style: PathStyle) -> Self {
|
pub fn from_bez_path(bez_path: BezPath, style: PathStyle, solid: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
path: bez_path,
|
||||||
|
style,
|
||||||
|
render_index: 1,
|
||||||
|
solid: solid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ngon(sides: u8, style: PathStyle) -> Self {
|
||||||
use std::f64::consts::{FRAC_PI_2, TAU};
|
use std::f64::consts::{FRAC_PI_2, TAU};
|
||||||
fn unit_rotation(theta: f64) -> DVec2 {
|
fn unit_rotation(theta: f64) -> DVec2 {
|
||||||
DVec2::new(theta.sin(), theta.cos())
|
DVec2::new(theta.sin(), theta.cos())
|
||||||
|
|||||||
@@ -20,13 +20,18 @@ pub enum Operation {
|
|||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
|
AddOverlayEllipse {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
transform: [f64; 6],
|
||||||
|
style: style::PathStyle,
|
||||||
|
},
|
||||||
AddRect {
|
AddRect {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
AddBoundingBox {
|
AddOverlayRect {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
@@ -37,6 +42,11 @@ pub enum Operation {
|
|||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
|
AddOverlayLine {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
transform: [f64; 6],
|
||||||
|
style: style::PathStyle,
|
||||||
|
},
|
||||||
AddPen {
|
AddPen {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
@@ -44,13 +54,18 @@ pub enum Operation {
|
|||||||
points: Vec<(f64, f64)>,
|
points: Vec<(f64, f64)>,
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
AddShape {
|
AddNgon {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
sides: u8,
|
sides: u8,
|
||||||
style: style::PathStyle,
|
style: style::PathStyle,
|
||||||
},
|
},
|
||||||
|
AddOverlayShape {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
bez_path: kurbo::BezPath,
|
||||||
|
style: style::PathStyle,
|
||||||
|
},
|
||||||
DeleteLayer {
|
DeleteLayer {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
},
|
},
|
||||||
@@ -81,6 +96,11 @@ pub enum Operation {
|
|||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
},
|
},
|
||||||
|
SetShapePathInViewport {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
bez_path: kurbo::BezPath,
|
||||||
|
transform: [f64; 6],
|
||||||
|
},
|
||||||
TransformLayerInScope {
|
TransformLayerInScope {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
@@ -95,9 +115,13 @@ pub enum Operation {
|
|||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
},
|
},
|
||||||
ToggleVisibility {
|
ToggleLayerVisibility {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
},
|
},
|
||||||
|
SetLayerVisibility {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
visible: bool,
|
||||||
|
},
|
||||||
SetLayerBlendMode {
|
SetLayerBlendMode {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
@@ -106,7 +130,11 @@ pub enum Operation {
|
|||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
opacity: f64,
|
opacity: f64,
|
||||||
},
|
},
|
||||||
FillLayer {
|
SetLayerStyle {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
style: style::PathStyle,
|
||||||
|
},
|
||||||
|
SetLayerFill {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
color: Color,
|
color: Color,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user