mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Massively reorganize and clean up the whole Rust codebase (#478)
* Massively reorganize and clean up the whole Rust codebase * Additional changes during code review
This commit is contained in:
parent
011c2be26d
commit
f48d4e1884
85 changed files with 2515 additions and 2189 deletions
|
@ -1,26 +1,31 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
pub use crate::document::PortfolioMessageHandler;
|
||||
pub use crate::input::{InputMapper, InputPreprocessor};
|
||||
pub use crate::tool::ToolMessageHandler;
|
||||
|
||||
use crate::document::PortfolioMessageHandler;
|
||||
use crate::global::GlobalMessageHandler;
|
||||
use crate::input::{InputMapperMessageHandler, InputPreprocessorMessageHandler};
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool_message_handler::ToolMessageHandler;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Dispatcher {
|
||||
input_preprocessor: InputPreprocessor,
|
||||
input_mapper: InputMapper,
|
||||
global_message_handler: GlobalMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
portfolio_message_handler: PortfolioMessageHandler,
|
||||
messages: VecDeque<Message>,
|
||||
message_queue: VecDeque<Message>,
|
||||
pub responses: Vec<FrontendMessage>,
|
||||
message_handlers: DispatcherMessageHandlers,
|
||||
}
|
||||
|
||||
// For optimization, these are messages guaranteed to be redundant when repeated
|
||||
// The last occurrence of the message in the message queue is sufficient to ensure correctness
|
||||
// In addition, these messages do not change any state in the backend (aside from caches)
|
||||
#[remain::sorted]
|
||||
#[derive(Debug, Default)]
|
||||
struct DispatcherMessageHandlers {
|
||||
global_message_handler: GlobalMessageHandler,
|
||||
input_mapper_message_handler: InputMapperMessageHandler,
|
||||
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
||||
portfolio_message_handler: PortfolioMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
}
|
||||
|
||||
// For optimization, these are messages guaranteed to be redundant when repeated.
|
||||
// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
||||
// In addition, these messages do not change any state in the backend (aside from caches).
|
||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::FolderChanged)),
|
||||
|
@ -35,28 +40,54 @@ impl Dispatcher {
|
|||
Self::default()
|
||||
}
|
||||
|
||||
#[remain::check]
|
||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T) {
|
||||
self.messages.push_back(message.into());
|
||||
self.message_queue.push_back(message.into());
|
||||
|
||||
use Message::*;
|
||||
while let Some(message) = self.messages.pop_front() {
|
||||
while let Some(message) = self.message_queue.pop_front() {
|
||||
// Skip processing of this message if it will be processed later
|
||||
if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) && self.messages.contains(&message) {
|
||||
if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) && self.message_queue.contains(&message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Print the message at a verbosity level of `log`
|
||||
self.log_message(&message);
|
||||
|
||||
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
NoOp => (),
|
||||
Portfolio(message) => self.portfolio_message_handler.process_action(message, &self.input_preprocessor, &mut self.messages),
|
||||
Global(message) => self.global_message_handler.process_action(message, (), &mut self.messages),
|
||||
Tool(message) => self
|
||||
.tool_message_handler
|
||||
.process_action(message, (self.portfolio_message_handler.active_document(), &self.input_preprocessor), &mut self.messages),
|
||||
Frontend(message) => self.responses.push(message),
|
||||
InputPreprocessor(message) => self.input_preprocessor.process_action(message, (), &mut self.messages),
|
||||
Frontend(message) => {
|
||||
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
|
||||
self.responses.push(message);
|
||||
}
|
||||
Global(message) => {
|
||||
self.message_handlers.global_message_handler.process_action(message, (), &mut self.message_queue);
|
||||
}
|
||||
InputMapper(message) => {
|
||||
let actions = self.collect_actions();
|
||||
self.input_mapper.process_action(message, (&self.input_preprocessor, actions), &mut self.messages)
|
||||
self.message_handlers
|
||||
.input_mapper_message_handler
|
||||
.process_action(message, (&self.message_handlers.input_preprocessor_message_handler, actions), &mut self.message_queue);
|
||||
}
|
||||
InputPreprocessor(message) => {
|
||||
self.message_handlers.input_preprocessor_message_handler.process_action(message, (), &mut self.message_queue);
|
||||
}
|
||||
NoOp => {}
|
||||
Portfolio(message) => {
|
||||
self.message_handlers
|
||||
.portfolio_message_handler
|
||||
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_queue);
|
||||
}
|
||||
Tool(message) => {
|
||||
self.message_handlers.tool_message_handler.process_action(
|
||||
message,
|
||||
(
|
||||
self.message_handlers.portfolio_message_handler.active_document(),
|
||||
&self.message_handlers.input_preprocessor_message_handler,
|
||||
),
|
||||
&mut self.message_queue,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -65,11 +96,11 @@ impl Dispatcher {
|
|||
pub fn collect_actions(&self) -> ActionList {
|
||||
// TODO: Reduce the number of heap allocations
|
||||
let mut list = Vec::new();
|
||||
list.extend(self.input_preprocessor.actions());
|
||||
list.extend(self.input_mapper.actions());
|
||||
list.extend(self.global_message_handler.actions());
|
||||
list.extend(self.tool_message_handler.actions());
|
||||
list.extend(self.portfolio_message_handler.actions());
|
||||
list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
|
||||
list.extend(self.message_handlers.input_mapper_message_handler.actions());
|
||||
list.extend(self.message_handlers.global_message_handler.actions());
|
||||
list.extend(self.message_handlers.tool_message_handler.actions());
|
||||
list.extend(self.message_handlers.portfolio_message_handler.actions());
|
||||
list
|
||||
}
|
||||
|
||||
|
@ -82,21 +113,21 @@ impl Dispatcher {
|
|||
) || MessageDiscriminant::from(message).local_name().ends_with("MouseMove"))
|
||||
{
|
||||
log::trace!("Message: {:?}", message);
|
||||
// log::trace!("Hints: {:?}", self.input_mapper.hints(self.collect_actions()));
|
||||
// log::trace!("Hints: {:?}", self.input_mapper_message_handler.hints(self.collect_actions()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
communication::set_uuid_seed,
|
||||
document::{Clipboard::*, DocumentMessageHandler},
|
||||
message_prelude::*,
|
||||
misc::test_utils::EditorTestUtils,
|
||||
Editor,
|
||||
};
|
||||
use graphene::{color::Color, Operation};
|
||||
use crate::communication::set_uuid_seed;
|
||||
use crate::document::clipboards::Clipboard;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::test_utils::EditorTestUtils;
|
||||
use crate::Editor;
|
||||
use graphene::color::Color;
|
||||
use graphene::Operation;
|
||||
|
||||
fn init_logger() {
|
||||
let _ = env_logger::builder().is_test(true).try_init();
|
||||
|
@ -129,14 +160,14 @@ mod test {
|
|||
init_logger();
|
||||
let mut editor = create_editor_with_three_layers();
|
||||
|
||||
let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
editor.handle_message(PortfolioMessage::Copy(User));
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
editor.handle_message(PortfolioMessage::Copy(Clipboard::User));
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
|
@ -163,18 +194,18 @@ mod test {
|
|||
init_logger();
|
||||
let mut editor = create_editor_with_three_layers();
|
||||
|
||||
let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let shape_id = document_before_copy.root.as_folder().unwrap().layer_ids[1];
|
||||
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![shape_id]]));
|
||||
editor.handle_message(PortfolioMessage::Copy(User));
|
||||
editor.handle_message(PortfolioMessage::Copy(Clipboard::User));
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
|
@ -206,7 +237,7 @@ mod test {
|
|||
|
||||
editor.handle_message(DocumentMessage::CreateEmptyFolder(vec![]));
|
||||
|
||||
let document_before_added_shapes = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_added_shapes = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let folder_id = document_before_added_shapes.root.as_folder().unwrap().layer_ids[FOLDER_INDEX];
|
||||
|
||||
// TODO: This adding of a Line and Pen should be rewritten using the corresponding functions in EditorTestUtils.
|
||||
|
@ -228,22 +259,22 @@ mod test {
|
|||
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![folder_id]]));
|
||||
|
||||
let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
|
||||
editor.handle_message(PortfolioMessage::Copy(User));
|
||||
editor.handle_message(PortfolioMessage::Copy(Clipboard::User));
|
||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
|
@ -294,26 +325,26 @@ mod test {
|
|||
const SHAPE_INDEX: usize = 1;
|
||||
const RECT_INDEX: usize = 0;
|
||||
|
||||
let document_before_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let rect_id = document_before_copy.root.as_folder().unwrap().layer_ids[RECT_INDEX];
|
||||
let ellipse_id = document_before_copy.root.as_folder().unwrap().layer_ids[ELLIPSE_INDEX];
|
||||
|
||||
editor.handle_message(DocumentMessage::SetSelectedLayers(vec![vec![rect_id], vec![ellipse_id]]));
|
||||
editor.handle_message(PortfolioMessage::Copy(User));
|
||||
editor.handle_message(PortfolioMessage::Copy(Clipboard::User));
|
||||
editor.handle_message(DocumentMessage::DeleteSelectedLayers);
|
||||
editor.draw_rect(0., 800., 12., 200.);
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
editor.handle_message(PortfolioMessage::PasteIntoFolder {
|
||||
clipboard: User,
|
||||
clipboard: Clipboard::User,
|
||||
path: vec![],
|
||||
insert_index: -1,
|
||||
});
|
||||
|
||||
let document_after_copy = editor.dispatcher.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().graphene_document.clone();
|
||||
|
||||
let layers_before_copy = document_before_copy.root.as_folder().unwrap().layers();
|
||||
let layers_after_copy = document_after_copy.root.as_folder().unwrap().layers();
|
||||
|
@ -343,7 +374,7 @@ mod test {
|
|||
fn map_to_vec(paths: Vec<&[LayerId]>) -> Vec<Vec<LayerId>> {
|
||||
paths.iter().map(|layer| layer.to_vec()).collect::<Vec<_>>()
|
||||
}
|
||||
let sorted_layers = map_to_vec(editor.dispatcher.portfolio_message_handler.active_document().all_layers_sorted());
|
||||
let sorted_layers = map_to_vec(editor.dispatcher.message_handlers.portfolio_message_handler.active_document().all_layers_sorted());
|
||||
println!("Sorted layers: {:?}", sorted_layers);
|
||||
|
||||
let verify_order = |handler: &mut DocumentMessageHandler| {
|
||||
|
@ -357,15 +388,15 @@ mod test {
|
|||
editor.handle_message(DocumentMessage::SetSelectedLayers(sorted_layers[..2].to_vec()));
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers(1));
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers(-1));
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
assert_eq!(all, selected.into_iter().chain(non_selected.into_iter()).collect::<Vec<_>>());
|
||||
|
||||
editor.handle_message(DocumentMessage::ReorderSelectedLayers(i32::MAX));
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.portfolio_message_handler.active_document_mut());
|
||||
let (all, non_selected, selected) = verify_order(editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut());
|
||||
assert_eq!(all, non_selected.into_iter().chain(selected.into_iter()).collect::<Vec<_>>());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
use graphite_proc_macros::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
pub trait AsMessage: TransitiveChild
|
||||
where
|
||||
|
@ -16,22 +16,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Message {
|
||||
#[child]
|
||||
Frontend(FrontendMessage),
|
||||
#[child]
|
||||
Global(GlobalMessage),
|
||||
#[child]
|
||||
InputMapper(InputMapperMessage),
|
||||
#[child]
|
||||
InputPreprocessor(InputPreprocessorMessage),
|
||||
NoOp,
|
||||
#[child]
|
||||
Portfolio(PortfolioMessage),
|
||||
#[child]
|
||||
Global(GlobalMessage),
|
||||
#[child]
|
||||
Tool(ToolMessage),
|
||||
#[child]
|
||||
Frontend(FrontendMessage),
|
||||
#[child]
|
||||
InputPreprocessor(InputPreprocessorMessage),
|
||||
#[child]
|
||||
InputMapper(InputMapperMessage),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
|
@ -39,12 +40,13 @@ impl Message {
|
|||
///
|
||||
/// # Safety
|
||||
/// This function reads from uninitialized memory!!!
|
||||
/// Only use if you know what you are doing
|
||||
/// Only use if you know what you are doing.
|
||||
unsafe fn as_slice(&self) -> &[u8] {
|
||||
core::slice::from_raw_parts(self as *const Message as *const u8, std::mem::size_of::<Message>())
|
||||
}
|
||||
|
||||
/// Returns a pseudo hash that should uniquely identify the message.
|
||||
/// This is needed because `Hash` is not implemented for f64s
|
||||
/// This is needed because `Hash` is not implemented for `f64`s
|
||||
///
|
||||
/// # Safety
|
||||
/// This function reads from uninitialized memory but the generated value should be fine.
|
||||
|
|
22
editor/src/communication/message_handler.rs
Normal file
22
editor/src/communication/message_handler.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
pub use crate::communication::dispatcher::*;
|
||||
pub use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub type ActionList = Vec<Vec<MessageDiscriminant>>;
|
||||
|
||||
// TODO: Add Send + Sync requirement
|
||||
// Use something like rw locks for synchronization
|
||||
pub trait MessageHandlerData {}
|
||||
|
||||
pub trait MessageHandler<A: ToDiscriminant, T>
|
||||
where
|
||||
A::Discriminant: AsMessage,
|
||||
<A::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <A::Discriminant as TransitiveChild>::TopParent, TopParent = <A::Discriminant as TransitiveChild>::TopParent> + AsMessage,
|
||||
{
|
||||
/// Return true if the Action is consumed.
|
||||
fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque<Message>);
|
||||
|
||||
fn actions(&self) -> ActionList;
|
||||
}
|
|
@ -1,34 +1,17 @@
|
|||
pub mod dispatcher;
|
||||
pub mod message;
|
||||
use crate::message_prelude::*;
|
||||
pub use dispatcher::*;
|
||||
use rand_chacha::{
|
||||
rand_core::{RngCore, SeedableRng},
|
||||
ChaCha20Rng,
|
||||
};
|
||||
pub mod message_handler;
|
||||
|
||||
pub use crate::communication::dispatcher::*;
|
||||
pub use crate::input::InputPreprocessorMessageHandler;
|
||||
|
||||
use rand_chacha::rand_core::{RngCore, SeedableRng};
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use spin::Mutex;
|
||||
|
||||
pub use crate::input::InputPreprocessor;
|
||||
use std::{cell::Cell, collections::VecDeque};
|
||||
|
||||
pub type ActionList = Vec<Vec<MessageDiscriminant>>;
|
||||
use std::cell::Cell;
|
||||
|
||||
static RNG: Mutex<Option<ChaCha20Rng>> = Mutex::new(None);
|
||||
|
||||
// TODO: Add Send + Sync requirement
|
||||
// Use something like rw locks for synchronization
|
||||
pub trait MessageHandlerData {}
|
||||
|
||||
pub trait MessageHandler<A: ToDiscriminant, T>
|
||||
where
|
||||
A::Discriminant: AsMessage,
|
||||
<A::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <A::Discriminant as TransitiveChild>::TopParent, TopParent = <A::Discriminant as TransitiveChild>::TopParent> + AsMessage,
|
||||
{
|
||||
/// Return true if the Action is consumed.
|
||||
fn process_action(&mut self, action: A, data: T, responses: &mut VecDeque<Message>);
|
||||
fn actions(&self) -> ActionList;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static UUID_SEED: Cell<Option<u64>> = Cell::new(None);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use graphene::color::Color;
|
||||
|
||||
// VIEWPORT
|
||||
// Viewport
|
||||
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
|
||||
pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;
|
||||
pub const VIEWPORT_ZOOM_SCALE_MIN: f64 = 0.000_000_1;
|
||||
|
@ -17,22 +17,22 @@ pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
|
|||
|
||||
pub const SNAP_TOLERANCE: f64 = 3.;
|
||||
|
||||
// TRANSFORMING LAYER
|
||||
// Transforming layer
|
||||
pub const ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
||||
pub const SLOWING_DIVISOR: f64 = 10.;
|
||||
|
||||
// SELECT TOOL
|
||||
// Select tool
|
||||
pub const SELECTION_TOLERANCE: f64 = 1.;
|
||||
pub const SELECTION_DRAG_ANGLE: f64 = 90.;
|
||||
|
||||
// PATH TOOL
|
||||
// Path tool
|
||||
pub const VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE: f64 = 5.;
|
||||
|
||||
// LINE TOOL
|
||||
// Line tool
|
||||
pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
|
||||
// SCROLLBARS
|
||||
// Scrollbars
|
||||
pub const SCROLLBAR_SPACING: f64 = 0.1;
|
||||
pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
|
||||
pub const SCALE_EFFECT: f64 = 0.5;
|
||||
|
@ -41,7 +41,7 @@ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
|||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
pub const FILE_EXPORT_SUFFIX: &str = ".svg";
|
||||
|
||||
// COLORS
|
||||
// Colors
|
||||
pub const COLOR_ACCENT: Color = Color::from_unsafe(0x00 as f32 / 255., 0xA8 as f32 / 255., 0xFF as f32 / 255.);
|
||||
|
||||
// Document
|
||||
|
|
20
editor/src/document/artboard_message.rs
Normal file
20
editor/src/document/artboard_message.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Artboard)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ArtboardMessage {
|
||||
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
RenderArtboards,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for ArtboardMessage {
|
||||
fn from(operation: DocumentOperation) -> Self {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
|
@ -1,30 +1,16 @@
|
|||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::{DocumentMessage, LayerMetadata};
|
||||
use crate::input::InputPreprocessor;
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::{self, Fill, ViewMode};
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Artboard)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ArtboardMessage {
|
||||
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
RenderArtboards,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for ArtboardMessage {
|
||||
fn from(operation: DocumentOperation) -> Self {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ArtboardMessageHandler {
|
||||
pub artboards_graphene_document: GrapheneDocument,
|
||||
|
@ -37,9 +23,9 @@ impl ArtboardMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
|
||||
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessorMessageHandler)> for ArtboardMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use ArtboardMessage::*;
|
||||
#[remain::sorted]
|
||||
|
|
21
editor/src/document/clipboards.rs
Normal file
21
editor/src/document/clipboards.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use super::layer_panel::LayerMetadata;
|
||||
|
||||
use graphene::layers::layer_info::Layer;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Clipboard {
|
||||
System,
|
||||
User,
|
||||
_ClipboardCount, // Keep this as the last entry since it is used for counting the number of enum variants
|
||||
}
|
||||
|
||||
pub const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CopyBufferEntry {
|
||||
pub layer: Layer,
|
||||
pub layer_metadata: LayerMetadata,
|
||||
}
|
88
editor/src/document/document_message.rs
Normal file
88
editor/src/document/document_message.rs
Normal file
|
@ -0,0 +1,88 @@
|
|||
use super::layer_panel::LayerMetadata;
|
||||
use super::utility_types::{AlignAggregate, AlignAxis, FlipAxis};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::LayerId;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
AbortTransaction,
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||
#[child]
|
||||
Artboard(ArtboardMessage),
|
||||
CommitTransaction,
|
||||
CreateEmptyFolder(Vec<LayerId>),
|
||||
DebugPrintDocument,
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
DeleteSelectedLayers,
|
||||
DeselectAllLayers,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DuplicateSelectedLayers,
|
||||
ExportDocument,
|
||||
FlipSelectedLayers(FlipAxis),
|
||||
FolderChanged(Vec<LayerId>),
|
||||
GroupSelectedLayers,
|
||||
LayerChanged(Vec<LayerId>),
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
MoveSelectedLayersTo {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
NudgeSelectedLayers(f64, f64),
|
||||
#[child]
|
||||
Overlays(OverlaysMessage),
|
||||
Redo,
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
RenderDocument,
|
||||
ReorderSelectedLayers(i32), // relative_position,
|
||||
RollbackTransaction,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectionChanged,
|
||||
SelectLayer(Vec<LayerId>, bool, bool),
|
||||
SetBlendModeForSelectedLayers(BlendMode),
|
||||
SetLayerExpansion(Vec<LayerId>, bool),
|
||||
SetOpacityForSelectedLayers(f64),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SetSnapping(bool),
|
||||
SetViewMode(ViewMode),
|
||||
StartTransaction,
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
Undo,
|
||||
UngroupLayers(Vec<LayerId>),
|
||||
UngroupSelectedLayers,
|
||||
UpdateLayerMetadata {
|
||||
layer_path: Vec<LayerId>,
|
||||
layer_metadata: LayerMetadata,
|
||||
},
|
||||
ZoomCanvasToFitAll,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
fn from(operation: DocumentOperation) -> DocumentMessage {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||
}
|
||||
}
|
|
@ -1,73 +1,27 @@
|
|||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::artboard_message_handler::ArtboardMessage;
|
||||
use super::artboard_message_handler::ArtboardMessageHandler;
|
||||
pub use super::layer_panel::*;
|
||||
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
||||
use super::overlay_message_handler::OverlayMessageHandler;
|
||||
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
|
||||
use super::clipboards::Clipboard;
|
||||
use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||
use super::vectorize_layer_metadata;
|
||||
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
||||
use crate::consts::{
|
||||
ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
|
||||
};
|
||||
use crate::document::Clipboard;
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::EditorError;
|
||||
|
||||
use graphene::layers::{style::ViewMode, BlendMode, LayerDataType};
|
||||
use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId};
|
||||
use graphene::{DocumentResponse, Operation as DocumentOperation};
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::folder::Folder;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::layers::Folder;
|
||||
use kurbo::PathSeg;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum FlipAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAggregate {
|
||||
Min,
|
||||
Max,
|
||||
Center,
|
||||
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 {
|
||||
/// The path to the layer
|
||||
pub layer_path: Vec<LayerId>,
|
||||
/// The outline of the shape
|
||||
pub path: kurbo::BezPath,
|
||||
/// The control points / manipulator handles
|
||||
pub segments: Vec<VectorManipulatorSegment>,
|
||||
/// The compound Bezier curve is closed
|
||||
pub closed: bool,
|
||||
/// The transformation matrix to apply
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DocumentMessageHandler {
|
||||
|
@ -83,7 +37,7 @@ pub struct DocumentMessageHandler {
|
|||
layer_range_selection_reference: Vec<LayerId>,
|
||||
movement_handler: MovementMessageHandler,
|
||||
#[serde(skip)]
|
||||
overlay_message_handler: OverlayMessageHandler,
|
||||
overlays_message_handler: OverlaysMessageHandler,
|
||||
artboard_message_handler: ArtboardMessageHandler,
|
||||
#[serde(skip)]
|
||||
transform_layer_handler: TransformLayerMessageHandler,
|
||||
|
@ -98,12 +52,12 @@ impl Default for DocumentMessageHandler {
|
|||
graphene_document: GrapheneDocument::default(),
|
||||
document_undo_history: Vec::new(),
|
||||
document_redo_history: Vec::new(),
|
||||
name: String::from("Untitled Document"),
|
||||
saved_document_identifier: 0,
|
||||
name: String::from("Untitled Document"),
|
||||
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
|
||||
layer_range_selection_reference: Vec::new(),
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
overlay_message_handler: OverlayMessageHandler::default(),
|
||||
overlays_message_handler: OverlaysMessageHandler::default(),
|
||||
artboard_message_handler: ArtboardMessageHandler::default(),
|
||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||
snapping_enabled: true,
|
||||
|
@ -113,84 +67,6 @@ impl Default for DocumentMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
AbortTransaction,
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||
#[child]
|
||||
Artboard(ArtboardMessage),
|
||||
CommitTransaction,
|
||||
CreateEmptyFolder(Vec<LayerId>),
|
||||
DebugPrintDocument,
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
DeleteSelectedLayers,
|
||||
DeselectAllLayers,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DuplicateSelectedLayers,
|
||||
ExportDocument,
|
||||
FlipSelectedLayers(FlipAxis),
|
||||
FolderChanged(Vec<LayerId>),
|
||||
GroupSelectedLayers,
|
||||
LayerChanged(Vec<LayerId>),
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
MoveSelectedLayersTo {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
NudgeSelectedLayers(f64, f64),
|
||||
#[child]
|
||||
Overlay(OverlayMessage),
|
||||
Redo,
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
RenderDocument,
|
||||
ReorderSelectedLayers(i32), // relative_position,
|
||||
RollbackTransaction,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectionChanged,
|
||||
SelectLayer(Vec<LayerId>, bool, bool),
|
||||
SetBlendModeForSelectedLayers(BlendMode),
|
||||
SetLayerExpansion(Vec<LayerId>, bool),
|
||||
SetOpacityForSelectedLayers(f64),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SetSnapping(bool),
|
||||
SetViewMode(ViewMode),
|
||||
StartTransaction,
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
Undo,
|
||||
UngroupLayers(Vec<LayerId>),
|
||||
UngroupSelectedLayers,
|
||||
UpdateLayerMetadata {
|
||||
layer_path: Vec<LayerId>,
|
||||
layer_metadata: LayerMetadata,
|
||||
},
|
||||
ZoomCanvasToFitAll,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
fn from(operation: DocumentOperation) -> DocumentMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
pub fn serialize_document(&self) -> String {
|
||||
let val = serde_json::to_string(self);
|
||||
|
@ -212,7 +88,7 @@ impl DocumentMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_name(name: String, ipp: &InputPreprocessor) -> Self {
|
||||
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self {
|
||||
let mut document = Self { name, ..Self::default() };
|
||||
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
|
||||
document.graphene_document.root.transform = starting_root_transform;
|
||||
|
@ -256,13 +132,13 @@ impl DocumentMessageHandler {
|
|||
self.graphene_document.combined_viewport_bounding_box(paths)
|
||||
}
|
||||
|
||||
// TODO: Consider moving this to some kind of overlay manager in the future
|
||||
// TODO: Consider moving this to some kind of overlays manager in the future
|
||||
pub fn selected_visible_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
|
||||
let shapes = self.selected_layers().filter_map(|path_to_shape| {
|
||||
let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?;
|
||||
let layer = self.graphene_document.layer(path_to_shape);
|
||||
|
||||
// Filter out the non-visible layers from the filter_map
|
||||
// Filter out the non-visible layers from the `filter_map`
|
||||
match &layer {
|
||||
Ok(layer) if layer.visible => {}
|
||||
_ => return None,
|
||||
|
@ -343,30 +219,38 @@ impl DocumentMessageHandler {
|
|||
structure.push(space | 1 << 63);
|
||||
}
|
||||
|
||||
/// Serializes the layer structure into a compressed 1d structure
|
||||
/// Serializes the layer structure into a condensed 1D structure.
|
||||
///
|
||||
/// # Format
|
||||
/// It is a string of numbers broken into three sections:
|
||||
/// (4),(2,1,-2,-0),(16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186) <- Example encoded data
|
||||
/// L = 4 = structure.len() <- First value in the encoding: L, the length of the structure section
|
||||
/// structure = 2,1,-2,-0 <- Subsequent L values: structure section
|
||||
/// data = 16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186 <- Remaining values: data section (layer IDs)
|
||||
///
|
||||
/// | Data | Description | Length |
|
||||
/// |--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------|
|
||||
/// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | |
|
||||
/// | `L` = `4` = `structure.len()` | `L`, the length of the **Structure** section | First value |
|
||||
/// | **Structure** section = `2, 1, -2, -0` | The **Structure** section | Next `L` values |
|
||||
/// | **Data** section = `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values |
|
||||
///
|
||||
/// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom.
|
||||
/// The structure section lists signed numbers. The sign indicates a folder indentation change (+ is down a level, - is up a level).
|
||||
/// the numbers in the structure block encode the indentation,
|
||||
/// 2 mean read two element from the data section, then place a [
|
||||
/// -x means read x elements from the data section and then insert a ]
|
||||
/// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level).
|
||||
/// The numbers in the structure block encode the indentation. For example:
|
||||
/// - `2` means read two element from the data section, then place a `[`.
|
||||
/// - `-x` means read `x` elements from the data section and then insert a `]`.
|
||||
///
|
||||
/// ```text
|
||||
/// 2 V 1 V -2 A -0 A
|
||||
/// 16533113728871998040,3427872634365736244, 18115028555707261608, 15878401910454357952,449479075714955186
|
||||
/// 16533113728871998040,3427872634365736244,[ 18115028555707261608,[15878401910454357952,449479075714955186] ]
|
||||
/// ```
|
||||
///
|
||||
/// resulting layer panel:
|
||||
/// Resulting layer panel:
|
||||
/// ```text
|
||||
/// 16533113728871998040
|
||||
/// 3427872634365736244
|
||||
/// [3427872634365736244,18115028555707261608]
|
||||
/// [3427872634365736244,18115028555707261608,15878401910454357952]
|
||||
/// [3427872634365736244,18115028555707261608,449479075714955186]
|
||||
/// ```
|
||||
pub fn serialize_root(&self) -> Vec<u64> {
|
||||
let (mut structure, mut data) = (vec![0], Vec::new());
|
||||
self.serialize_structure(self.graphene_document.root.as_folder().unwrap(), &mut structure, &mut data, &mut vec![]);
|
||||
|
@ -552,9 +436,9 @@ impl DocumentMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for DocumentMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
|
@ -798,13 +682,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
}
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
Overlay(message) => {
|
||||
self.overlay_message_handler.process_action(
|
||||
Overlays(message) => {
|
||||
self.overlays_message_handler.process_action(
|
||||
message,
|
||||
(Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp),
|
||||
responses,
|
||||
);
|
||||
// responses.push_back(OverlayMessage::RenderOverlays.into());
|
||||
// responses.push_back(OverlaysMessage::RenderOverlays.into());
|
||||
}
|
||||
Redo => {
|
||||
responses.push_back(SelectMessage::Abort.into());
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::LayerId;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)]
|
||||
pub struct LayerMetadata {
|
||||
|
|
|
@ -1,29 +1,48 @@
|
|||
pub mod clipboards;
|
||||
pub mod layer_panel;
|
||||
pub mod transformation;
|
||||
pub mod utility_types;
|
||||
pub mod vectorize_layer_metadata;
|
||||
|
||||
mod artboard_message;
|
||||
mod artboard_message_handler;
|
||||
mod document_message;
|
||||
mod document_message_handler;
|
||||
mod layer_panel;
|
||||
mod movement_handler;
|
||||
mod overlay_message_handler;
|
||||
mod movement_message;
|
||||
mod movement_message_handler;
|
||||
mod overlays_message;
|
||||
mod overlays_message_handler;
|
||||
mod portfolio_message;
|
||||
mod portfolio_message_handler;
|
||||
mod transform_layer_handler;
|
||||
mod vectorize_layer_metadata;
|
||||
mod transform_layer_message;
|
||||
mod transform_layer_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||
pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use artboard_message_handler::ArtboardMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use layer_panel::{LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::DocumentMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
||||
pub use movement_message::{MovementMessage, MovementMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use movement_message_handler::MovementMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant};
|
||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use overlays_message_handler::OverlaysMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use portfolio_message_handler::{Clipboard, PortfolioMessage, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
||||
pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use portfolio_message_handler::PortfolioMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use artboard_message_handler::{ArtboardMessage, ArtboardMessageDiscriminant};
|
||||
|
||||
pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
pub use transform_layer_message_handler::TransformLayerMessageHandler;
|
||||
|
|
40
editor/src/document/movement_message.rs
Normal file
40
editor/src/document/movement_message.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
DecreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
FitViewportToBounds {
|
||||
bounds: [DVec2; 2],
|
||||
padding_scale_factor: Option<f32>,
|
||||
prevent_zoom_past_100: bool,
|
||||
},
|
||||
IncreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
MouseMove {
|
||||
snap_angle: Key,
|
||||
wait_for_snap_angle_release: bool,
|
||||
snap_zoom: Key,
|
||||
zoom_from_viewport: Option<DVec2>,
|
||||
},
|
||||
RotateCanvasBegin,
|
||||
SetCanvasRotation(f64),
|
||||
SetCanvasZoom(f64),
|
||||
TransformCanvasEnd,
|
||||
TranslateCanvas(DVec2),
|
||||
TranslateCanvasBegin,
|
||||
TranslateCanvasByViewportFraction(DVec2),
|
||||
WheelCanvasTranslate {
|
||||
use_y_as_x: bool,
|
||||
},
|
||||
WheelCanvasZoom,
|
||||
ZoomCanvasBegin,
|
||||
}
|
|
@ -1,12 +1,8 @@
|
|||
use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL;
|
||||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::DocumentMessage;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::consts::{VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE};
|
||||
use crate::input::mouse::{ViewportBounds, ViewportPosition};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::{
|
||||
consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE},
|
||||
input::{mouse::ViewportBounds, mouse::ViewportPosition, InputPreprocessor},
|
||||
};
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
|
@ -14,41 +10,6 @@ use glam::{DAffine2, DVec2};
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
DecreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
FitViewportToBounds {
|
||||
bounds: [DVec2; 2],
|
||||
padding_scale_factor: Option<f32>,
|
||||
prevent_zoom_past_100: bool,
|
||||
},
|
||||
IncreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
MouseMove {
|
||||
snap_angle: Key,
|
||||
wait_for_snap_angle_release: bool,
|
||||
snap_zoom: Key,
|
||||
zoom_from_viewport: Option<DVec2>,
|
||||
},
|
||||
RotateCanvasBegin,
|
||||
SetCanvasRotation(f64),
|
||||
SetCanvasZoom(f64),
|
||||
TransformCanvasEnd,
|
||||
TranslateCanvas(DVec2),
|
||||
TranslateCanvasBegin,
|
||||
TranslateCanvasByViewportFraction(DVec2),
|
||||
WheelCanvasTranslate {
|
||||
use_y_as_x: bool,
|
||||
},
|
||||
WheelCanvasZoom,
|
||||
ZoomCanvasBegin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MovementMessageHandler {
|
||||
pub pan: DVec2,
|
||||
|
@ -138,6 +99,7 @@ impl MovementMessageHandler {
|
|||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
|
@ -148,9 +110,9 @@ impl MovementMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for MovementMessageHandler {
|
||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandler)> for MovementMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
let (document, ipp) = data;
|
||||
use MovementMessage::*;
|
||||
#[remain::sorted]
|
||||
|
@ -325,6 +287,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(MovementMessageDiscriminant;
|
||||
MouseMove,
|
|
@ -1,59 +0,0 @@
|
|||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::{DocumentMessage, LayerMetadata};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Overlay)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OverlayMessage {
|
||||
ClearAllOverlays,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for OverlayMessage {
|
||||
fn from(operation: DocumentOperation) -> OverlayMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlayMessageHandler {
|
||||
pub overlays_graphene_document: GrapheneDocument,
|
||||
}
|
||||
|
||||
impl MessageHandler<OverlayMessage, (&mut LayerMetadata, &Document, &InputPreprocessor)> for OverlayMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: OverlayMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use OverlayMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ClearAllOverlays => todo!(),
|
||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||
Ok(_) => (),
|
||||
Err(e) => log::error!("OverlayError: {:?}", e),
|
||||
},
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentOverlays {
|
||||
svg: self.overlays_graphene_document.render_root(ViewMode::Normal),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlayMessageDiscriminant;
|
||||
ClearAllOverlays
|
||||
)
|
||||
}
|
||||
}
|
19
editor/src/document/overlays_message.rs
Normal file
19
editor/src/document/overlays_message.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Overlays)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OverlaysMessage {
|
||||
ClearAllOverlays,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for OverlaysMessage {
|
||||
fn from(operation: DocumentOperation) -> OverlaysMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
40
editor/src/document/overlays_message_handler.rs
Normal file
40
editor/src/document/overlays_message_handler.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
use super::layer_panel::LayerMetadata;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlays_graphene_document: GrapheneDocument,
|
||||
}
|
||||
|
||||
impl MessageHandler<OverlaysMessage, (&mut LayerMetadata, &Document, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: OverlaysMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use OverlaysMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ClearAllOverlays => todo!(),
|
||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||
Ok(_) => (),
|
||||
Err(e) => log::error!("OverlaysError: {:?}", e),
|
||||
},
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentOverlays {
|
||||
svg: self.overlays_graphene_document.render_root(ViewMode::Normal),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlaysMessageDiscriminant; ClearAllOverlays)
|
||||
}
|
||||
}
|
43
editor/src/document/portfolio_message.rs
Normal file
43
editor/src/document/portfolio_message.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use super::clipboards::Clipboard;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::LayerId;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Portfolio)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PortfolioMessage {
|
||||
AutoSaveActiveDocument,
|
||||
AutoSaveDocument(u64),
|
||||
CloseActiveDocumentWithConfirmation,
|
||||
CloseAllDocuments,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDocument(u64),
|
||||
CloseDocumentWithConfirmation(u64),
|
||||
Copy(Clipboard),
|
||||
Cut(Clipboard),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
NewDocument,
|
||||
NextDocument,
|
||||
OpenDocument,
|
||||
OpenDocumentFile(String, String),
|
||||
OpenDocumentFileWithId {
|
||||
document: String,
|
||||
document_name: String,
|
||||
document_id: u64,
|
||||
document_is_saved: bool,
|
||||
},
|
||||
Paste(Clipboard),
|
||||
PasteIntoFolder {
|
||||
clipboard: Clipboard,
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
PrevDocument,
|
||||
RequestAboutGraphiteDialog,
|
||||
SelectDocument(u64),
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
|
@ -1,63 +1,15 @@
|
|||
use super::{DocumentMessageHandler, LayerMetadata};
|
||||
use super::clipboards::{CopyBufferEntry, CLIPBOARD_COUNT};
|
||||
use super::DocumentMessageHandler;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
use crate::frontend::frontend_message_handler::FrontendDocumentDetails;
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::frontend::utility_types::FrontendDocumentDetails;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::layers::Layer;
|
||||
use graphene::{LayerId, Operation as DocumentOperation};
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Clipboard {
|
||||
System,
|
||||
User,
|
||||
_ClipboardCount,
|
||||
}
|
||||
|
||||
const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Portfolio)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PortfolioMessage {
|
||||
AutoSaveActiveDocument,
|
||||
AutoSaveDocument(u64),
|
||||
CloseActiveDocumentWithConfirmation,
|
||||
CloseAllDocuments,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDocument(u64),
|
||||
CloseDocumentWithConfirmation(u64),
|
||||
Copy(Clipboard),
|
||||
Cut(Clipboard),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
NewDocument,
|
||||
NextDocument,
|
||||
OpenDocument,
|
||||
OpenDocumentFile(String, String),
|
||||
OpenDocumentFileWithId {
|
||||
document: String,
|
||||
document_name: String,
|
||||
document_id: u64,
|
||||
document_is_saved: bool,
|
||||
},
|
||||
Paste(Clipboard),
|
||||
PasteIntoFolder {
|
||||
clipboard: Clipboard,
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
PrevDocument,
|
||||
RequestAboutGraphiteDialog,
|
||||
SelectDocument(u64),
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
documents: HashMap<u64, DocumentMessageHandler>,
|
||||
|
@ -66,12 +18,6 @@ pub struct PortfolioMessageHandler {
|
|||
copy_buffer: [Vec<CopyBufferEntry>; CLIPBOARD_COUNT as usize],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CopyBufferEntry {
|
||||
layer: Layer,
|
||||
layer_metadata: LayerMetadata,
|
||||
}
|
||||
|
||||
impl PortfolioMessageHandler {
|
||||
pub fn active_document(&self) -> &DocumentMessageHandler {
|
||||
self.documents.get(&self.active_document_id).unwrap()
|
||||
|
@ -149,7 +95,7 @@ impl PortfolioMessageHandler {
|
|||
responses.push_back(PortfolioMessage::SelectDocument(document_id).into());
|
||||
}
|
||||
|
||||
// Returns an iterator over the open documents in order
|
||||
/// Returns an iterator over the open documents in order.
|
||||
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
|
||||
self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap"))
|
||||
}
|
||||
|
@ -176,9 +122,9 @@ impl Default for PortfolioMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHandler {
|
||||
impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for PortfolioMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
use PortfolioMessage::*;
|
||||
#[remain::sorted]
|
||||
|
@ -429,6 +375,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(PortfolioMessageDiscriminant;
|
||||
NewDocument,
|
||||
|
|
|
@ -1,550 +0,0 @@
|
|||
pub use super::layer_panel::*;
|
||||
|
||||
use super::LayerMetadata;
|
||||
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL, SLOWING_DIVISOR};
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::message_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
|
||||
|
||||
struct Selected<'a> {
|
||||
pub selected: Vec<Vec<LayerId>>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
}
|
||||
impl<'a> Selected<'a> {
|
||||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
) -> Self {
|
||||
let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect();
|
||||
for path in &selected {
|
||||
if !original_transforms.contains_key::<Vec<LayerId>>(path) {
|
||||
original_transforms.insert(path.clone(), document.layer(path).unwrap().transform);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
original_transforms,
|
||||
pivot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
let bounds = self
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.current_bounding_box_with_transform(multiplied_transform)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
for layer_path in &self.selected {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap();
|
||||
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in &self.selected {
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.to_vec(),
|
||||
transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Axis {
|
||||
Both,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for Axis {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn set_or_toggle(&mut self, target: Axis) {
|
||||
// If constrained to an axis and target is requesting the same axis, toggle back to Both
|
||||
if *self == target {
|
||||
*self = Axis::Both;
|
||||
}
|
||||
// If current axis is different from the target axis, switch to the target
|
||||
else {
|
||||
*self = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Translation {
|
||||
pub dragged_distance: DVec2,
|
||||
pub typed_distance: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn to_dvec(self) -> DVec2 {
|
||||
if let Some(value) = self.typed_distance {
|
||||
if self.constraint == Axis::Y {
|
||||
return DVec2::new(0., value);
|
||||
} else {
|
||||
return DVec2::new(value, 0.);
|
||||
}
|
||||
}
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
Axis::X => DVec2::new(self.dragged_distance.x, 0.),
|
||||
Axis::Y => DVec2::new(0., self.dragged_distance.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: DVec2) -> Self {
|
||||
Self {
|
||||
dragged_distance: self.dragged_distance + delta,
|
||||
typed_distance: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
struct Scale {
|
||||
pub dragged_factor: f64,
|
||||
pub typed_factor: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dragged_factor: 1.,
|
||||
typed_factor: None,
|
||||
constraint: Axis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||
let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor };
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
Axis::X => DVec2::new(factor, 1.),
|
||||
Axis::Y => DVec2::new(1., factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Rotation {
|
||||
pub dragged_angle: f64,
|
||||
pub typed_angle: Option<f64>,
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
pub fn to_f64(self, snap: bool) -> f64 {
|
||||
if let Some(value) = self.typed_angle {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_angle: self.dragged_angle + delta,
|
||||
typed_angle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Operation {
|
||||
None,
|
||||
Grabbing(Translation),
|
||||
Rotating(Rotation),
|
||||
Scaling(Scale),
|
||||
}
|
||||
|
||||
impl Default for Operation {
|
||||
fn default() -> Self {
|
||||
Operation::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn apply_operation(&self, selected: &mut Selected, snapping: bool) {
|
||||
if self != &Operation::None {
|
||||
let transformation = match self {
|
||||
Operation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()),
|
||||
Operation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
||||
Operation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)),
|
||||
Operation::None => unreachable!(),
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.constraint.set_or_toggle(axis),
|
||||
Operation::Rotating(_) => (),
|
||||
Operation::Scaling(scale) => scale.constraint.set_or_toggle(axis),
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn handle_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
Operation::Rotating(rotation) => rotation.typed_angle = typed,
|
||||
Operation::Scaling(scale) => scale.typed_factor = typed,
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct Typing {
|
||||
digits: Vec<u8>,
|
||||
contains_decimal: bool,
|
||||
negative: bool,
|
||||
}
|
||||
|
||||
const DECIMAL_POINT: u8 = 10;
|
||||
|
||||
impl Typing {
|
||||
pub fn type_number(&mut self, number: u8) -> Option<f64> {
|
||||
self.digits.push(number);
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_backspace(&mut self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.digits.pop() {
|
||||
Some(DECIMAL_POINT) => self.contains_decimal = false,
|
||||
Some(_) => (),
|
||||
None => self.negative = false,
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_decimal_point(&mut self) -> Option<f64> {
|
||||
if !self.contains_decimal {
|
||||
self.contains_decimal = true;
|
||||
self.digits.push(DECIMAL_POINT);
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_negate(&mut self) -> Option<f64> {
|
||||
self.negative = !self.negative;
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = 0_f64;
|
||||
let mut running_decimal_place = 0_i32;
|
||||
|
||||
for digit in &self.digits {
|
||||
if *digit == DECIMAL_POINT {
|
||||
if running_decimal_place == 0 {
|
||||
running_decimal_place = 1;
|
||||
}
|
||||
} else if running_decimal_place == 0 {
|
||||
result *= 10.;
|
||||
result += *digit as f64;
|
||||
} else {
|
||||
result += *digit as f64 * 0.1_f64.powi(running_decimal_place);
|
||||
running_decimal_place += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.negative {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.digits.clear();
|
||||
self.contains_decimal = false;
|
||||
self.negative = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TransformLayerMessage {
|
||||
ApplyOperation,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
CancelOperation,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
MouseMove { slow_key: Key, snap_key: Key },
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
TypeNumber(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
operation: Operation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
typing: Typing,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor)> for TransformLayerMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: TransformLayerMessage, data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
let (layer_metadata, document, ipp) = data;
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document);
|
||||
|
||||
let mut begin_operation = |operation: Operation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
|
||||
if !(operation == Operation::None) {
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
};
|
||||
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ApplyOperation => {
|
||||
self.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginGrab => {
|
||||
if let Operation::Grabbing(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let Operation::Rotating(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let Operation::Scaling(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Scaling(Default::default());
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
CancelOperation => {
|
||||
selected.revert_operation();
|
||||
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_key as usize);
|
||||
if new_snap != self.snap {
|
||||
self.snap = new_snap;
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
|
||||
if self.typing.digits.is_empty() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
match self.operation {
|
||||
Operation::None => unreachable!(),
|
||||
Operation::Grabbing(translation) => {
|
||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||
self.operation = Operation::Grabbing(translation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot();
|
||||
let angle = {
|
||||
let start_vec = self.mouse_position - selected_pivot;
|
||||
let end_vec = ipp.mouse.position - selected_pivot;
|
||||
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
self.operation = Operation::Rotating(rotation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::Scaling(scale) => {
|
||||
let change = {
|
||||
let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
|
||||
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length();
|
||||
let start_transform_dist = (self.start_mouse - *selected.pivot).length();
|
||||
|
||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||
};
|
||||
|
||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||
self.operation = Operation::Scaling(scale.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TypeBackspace => self.operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
TypeNegate => self.operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||
TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
);
|
||||
|
||||
if self.operation != Operation::None {
|
||||
let active = actions!(TransformLayerMessageDiscriminant;
|
||||
MouseMove,
|
||||
CancelOperation,
|
||||
ApplyOperation,
|
||||
TypeNumber,
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
);
|
||||
common.extend(active);
|
||||
}
|
||||
|
||||
common
|
||||
}
|
||||
}
|
22
editor/src/document/transform_layer_message.rs
Normal file
22
editor/src/document/transform_layer_message.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TransformLayerMessage {
|
||||
ApplyTransformOperation,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
CancelTransformOperation,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
MouseMove { slow_key: Key, snap_key: Key },
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
TypeNumber(u8),
|
||||
}
|
189
editor/src/document/transform_layer_message_handler.rs
Normal file
189
editor/src/document/transform_layer_message_handler.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
use super::layer_panel::LayerMetadata;
|
||||
use super::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
|
||||
use crate::consts::SLOWING_DIVISOR;
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
|
||||
use glam::DVec2;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
transform_operation: TransformOperation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
typing: Typing,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler)> for TransformLayerMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(
|
||||
&mut self,
|
||||
message: TransformLayerMessage,
|
||||
data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler),
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
let (layer_metadata, document, ipp) = data;
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document);
|
||||
|
||||
let mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
|
||||
if !(operation == TransformOperation::None) {
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
};
|
||||
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ApplyTransformOperation => {
|
||||
self.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginGrab => {
|
||||
if let TransformOperation::Grabbing(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let TransformOperation::Rotating(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let TransformOperation::Scaling(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Scaling(Default::default());
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
CancelTransformOperation => {
|
||||
selected.revert_operation();
|
||||
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_key as usize);
|
||||
if new_snap != self.snap {
|
||||
self.snap = new_snap;
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
|
||||
if self.typing.digits.is_empty() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => unreachable!(),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||
self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot();
|
||||
let angle = {
|
||||
let start_vec = self.mouse_position - selected_pivot;
|
||||
let end_vec = ipp.mouse.position - selected_pivot;
|
||||
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let change = {
|
||||
let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
|
||||
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length();
|
||||
let start_transform_dist = (self.start_mouse - *selected.pivot).length();
|
||||
|
||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||
};
|
||||
|
||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||
self.transform_operation = TransformOperation::Scaling(scale.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
TypeNegate => self.transform_operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||
TypeNumber(number) => self.transform_operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
);
|
||||
|
||||
if self.transform_operation != TransformOperation::None {
|
||||
let active = actions!(TransformLayerMessageDiscriminant;
|
||||
MouseMove,
|
||||
CancelTransformOperation,
|
||||
ApplyTransformOperation,
|
||||
TypeNumber,
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
);
|
||||
common.extend(active);
|
||||
}
|
||||
|
||||
common
|
||||
}
|
||||
}
|
356
editor/src/document/transformation.rs
Normal file
356
editor/src/document/transformation.rs
Normal file
|
@ -0,0 +1,356 @@
|
|||
use super::layer_panel::LayerMetadata;
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
pub type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum Axis {
|
||||
Both,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for Axis {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn set_or_toggle(&mut self, target: Axis) {
|
||||
// If constrained to an axis and target is requesting the same axis, toggle back to Both
|
||||
if *self == target {
|
||||
*self = Axis::Both;
|
||||
}
|
||||
// If current axis is different from the target axis, switch to the target
|
||||
else {
|
||||
*self = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Translation {
|
||||
pub dragged_distance: DVec2,
|
||||
pub typed_distance: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn to_dvec(self) -> DVec2 {
|
||||
if let Some(value) = self.typed_distance {
|
||||
if self.constraint == Axis::Y {
|
||||
return DVec2::new(0., value);
|
||||
} else {
|
||||
return DVec2::new(value, 0.);
|
||||
}
|
||||
}
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
Axis::X => DVec2::new(self.dragged_distance.x, 0.),
|
||||
Axis::Y => DVec2::new(0., self.dragged_distance.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: DVec2) -> Self {
|
||||
Self {
|
||||
dragged_distance: self.dragged_distance + delta,
|
||||
typed_distance: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Rotation {
|
||||
pub dragged_angle: f64,
|
||||
pub typed_angle: Option<f64>,
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
pub fn to_f64(self, snap: bool) -> f64 {
|
||||
if let Some(value) = self.typed_angle {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_angle: self.dragged_angle + delta,
|
||||
typed_angle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Scale {
|
||||
pub dragged_factor: f64,
|
||||
pub typed_factor: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dragged_factor: 1.,
|
||||
typed_factor: None,
|
||||
constraint: Axis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||
let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor };
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
Axis::X => DVec2::new(factor, 1.),
|
||||
Axis::Y => DVec2::new(1., factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum TransformOperation {
|
||||
None,
|
||||
Grabbing(Translation),
|
||||
Rotating(Rotation),
|
||||
Scaling(Scale),
|
||||
}
|
||||
|
||||
impl Default for TransformOperation {
|
||||
fn default() -> Self {
|
||||
TransformOperation::None
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformOperation {
|
||||
pub fn apply_transform_operation(&self, selected: &mut Selected, snapping: bool) {
|
||||
if self != &TransformOperation::None {
|
||||
let transformation = match self {
|
||||
TransformOperation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()),
|
||||
TransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
||||
TransformOperation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)),
|
||||
TransformOperation::None => unreachable!(),
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => translation.constraint.set_or_toggle(axis),
|
||||
TransformOperation::Rotating(_) => (),
|
||||
TransformOperation::Scaling(scale) => scale.constraint.set_or_toggle(axis),
|
||||
};
|
||||
|
||||
self.apply_transform_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn handle_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
TransformOperation::Rotating(rotation) => rotation.typed_angle = typed,
|
||||
TransformOperation::Scaling(scale) => scale.typed_factor = typed,
|
||||
};
|
||||
|
||||
self.apply_transform_operation(selected, snapping);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selected<'a> {
|
||||
pub selected: Vec<Vec<LayerId>>,
|
||||
pub responses: &'a mut VecDeque<Message>,
|
||||
pub document: &'a mut Document,
|
||||
pub original_transforms: &'a mut OriginalTransforms,
|
||||
pub pivot: &'a mut DVec2,
|
||||
}
|
||||
|
||||
impl<'a> Selected<'a> {
|
||||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
) -> Self {
|
||||
let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect();
|
||||
for path in &selected {
|
||||
if !original_transforms.contains_key::<Vec<LayerId>>(path) {
|
||||
original_transforms.insert(path.clone(), document.layer(path).unwrap().transform);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
original_transforms,
|
||||
pivot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
let bounds = self
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.current_bounding_box_with_transform(multiplied_transform)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
for layer_path in &self.selected {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap();
|
||||
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in &self.selected {
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.to_vec(),
|
||||
transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Typing {
|
||||
pub digits: Vec<u8>,
|
||||
pub contains_decimal: bool,
|
||||
pub negative: bool,
|
||||
}
|
||||
|
||||
const DECIMAL_POINT: u8 = 10;
|
||||
|
||||
impl Typing {
|
||||
pub fn type_number(&mut self, number: u8) -> Option<f64> {
|
||||
self.digits.push(number);
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_backspace(&mut self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.digits.pop() {
|
||||
Some(DECIMAL_POINT) => self.contains_decimal = false,
|
||||
Some(_) => (),
|
||||
None => self.negative = false,
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_decimal_point(&mut self) -> Option<f64> {
|
||||
if !self.contains_decimal {
|
||||
self.contains_decimal = true;
|
||||
self.digits.push(DECIMAL_POINT);
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_negate(&mut self) -> Option<f64> {
|
||||
self.negative = !self.negative;
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = 0_f64;
|
||||
let mut running_decimal_place = 0_i32;
|
||||
|
||||
for digit in &self.digits {
|
||||
if *digit == DECIMAL_POINT {
|
||||
if running_decimal_place == 0 {
|
||||
running_decimal_place = 1;
|
||||
}
|
||||
} else if running_decimal_place == 0 {
|
||||
result *= 10.;
|
||||
result += *digit as f64;
|
||||
} else {
|
||||
result += *digit as f64 * 0.1_f64.powi(running_decimal_place);
|
||||
running_decimal_place += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.negative {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.digits.clear();
|
||||
self.contains_decimal = false;
|
||||
self.negative = false;
|
||||
}
|
||||
}
|
51
editor/src/document/utility_types.rs
Normal file
51
editor/src/document/utility_types.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
pub use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::LayerId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum FlipAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAggregate {
|
||||
Min,
|
||||
Max,
|
||||
Center,
|
||||
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 {
|
||||
/// The path to the layer
|
||||
pub layer_path: Vec<LayerId>,
|
||||
/// The outline of the shape
|
||||
pub path: kurbo::BezPath,
|
||||
/// The control points / manipulator handles
|
||||
pub segments: Vec<VectorManipulatorSegment>,
|
||||
/// The compound Bezier curve is closed
|
||||
pub closed: bool,
|
||||
/// The transformation matrix to apply
|
||||
pub transform: DAffine2,
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/// Necessary because serde can't serialize hashmaps when the keys don't implement display.
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
/// Necessary because serde can't serialize hashmaps when the keys don't implement display.
|
||||
pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
use crate::document::{LayerPanelEntry, RawBuffer};
|
||||
use super::utility_types::FrontendDocumentDetails;
|
||||
use crate::document::layer_panel::{LayerPanelEntry, RawBuffer};
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::HintData;
|
||||
use crate::tool::tool_options::ToolOptions;
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
use crate::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct FrontendDocumentDetails {
|
||||
pub is_saved: bool,
|
||||
pub name: String,
|
||||
pub id: u64,
|
||||
}
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Frontend)]
|
|
@ -1,3 +1,6 @@
|
|||
pub mod frontend_message_handler;
|
||||
pub mod utility_types;
|
||||
|
||||
pub use frontend_message_handler::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
mod frontend_message;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
|
|
8
editor/src/frontend/utility_types.rs
Normal file
8
editor/src/frontend/utility_types.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||
pub struct FrontendDocumentDetails {
|
||||
pub is_saved: bool,
|
||||
pub name: String,
|
||||
pub id: u64,
|
||||
}
|
12
editor/src/global/global_message.rs
Normal file
12
editor/src/global/global_message.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Global)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum GlobalMessage {
|
||||
LogDebug,
|
||||
LogInfo,
|
||||
LogTrace,
|
||||
}
|
|
@ -1,15 +1,6 @@
|
|||
use crate::message_prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Global)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum GlobalMessage {
|
||||
LogDebug,
|
||||
LogInfo,
|
||||
LogTrace,
|
||||
}
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct GlobalMessageHandler {}
|
||||
|
@ -22,17 +13,18 @@ impl MessageHandler<GlobalMessage, ()> for GlobalMessageHandler {
|
|||
match message {
|
||||
LogDebug => {
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
log::info!("set log verbosity to debug");
|
||||
log::info!("Set log verbosity to debug");
|
||||
}
|
||||
LogInfo => {
|
||||
log::set_max_level(log::LevelFilter::Info);
|
||||
log::info!("set log verbosity to info");
|
||||
log::info!("Set log verbosity to info");
|
||||
}
|
||||
LogTrace => {
|
||||
log::set_max_level(log::LevelFilter::Trace);
|
||||
log::info!("set log verbosity to trace");
|
||||
log::info!("Set log verbosity to trace");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advertise_actions!(GlobalMessageDiscriminant; LogInfo, LogDebug, LogTrace);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
pub mod global_message_handler;
|
||||
mod global_message;
|
||||
mod global_message_handler;
|
||||
|
||||
pub use global_message_handler::{GlobalMessage, GlobalMessageDiscriminant, GlobalMessageHandler};
|
||||
#[doc(inline)]
|
||||
pub use global_message::{GlobalMessage, GlobalMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use global_message_handler::GlobalMessageHandler;
|
||||
|
|
|
@ -1,146 +1,38 @@
|
|||
use glam::DVec2;
|
||||
|
||||
use super::{
|
||||
keyboard::{Key, KeyStates, NUMBER_OF_KEYS},
|
||||
InputPreprocessor,
|
||||
};
|
||||
use crate::document::Clipboard::*;
|
||||
use super::keyboard::{Key, KeyStates, NUMBER_OF_KEYS};
|
||||
use crate::document::clipboards::Clipboard;
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolType;
|
||||
use crate::viewport_tools::tool::ToolType;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
use glam::DVec2;
|
||||
|
||||
const NUDGE_AMOUNT: f64 = 1.;
|
||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, InputMapper)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum InputMapperMessage {
|
||||
#[child]
|
||||
KeyDown(Key),
|
||||
#[child]
|
||||
KeyUp(Key),
|
||||
MouseScroll,
|
||||
PointerMove,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
struct MappingEntry {
|
||||
trigger: InputMapperMessage,
|
||||
modifiers: KeyStates,
|
||||
action: Message,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct KeyMappingEntries(Vec<MappingEntry>);
|
||||
|
||||
impl KeyMappingEntries {
|
||||
fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
for entry in self.0.iter() {
|
||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
return Some(entry.action.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn push(&mut self, entry: MappingEntry) {
|
||||
self.0.push(entry)
|
||||
}
|
||||
|
||||
const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
fn key_array() -> [Self; NUMBER_OF_KEYS] {
|
||||
const DEFAULT: KeyMappingEntries = KeyMappingEntries::new();
|
||||
[DEFAULT; NUMBER_OF_KEYS]
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for KeyMappingEntries {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Mapping {
|
||||
key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pointer_move: KeyMappingEntries,
|
||||
mouse_scroll: KeyMappingEntries,
|
||||
}
|
||||
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
)*
|
||||
state
|
||||
}};
|
||||
}
|
||||
macro_rules! entry {
|
||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
||||
}};
|
||||
{action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?}
|
||||
}};
|
||||
{action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
&[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}]
|
||||
}};
|
||||
{action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{
|
||||
&[
|
||||
MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()},
|
||||
$(
|
||||
MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
)*
|
||||
]
|
||||
}};
|
||||
}
|
||||
macro_rules! mapping {
|
||||
//[$(<action=$action:expr; message=$key:expr; $(modifiers=[$($m:ident),* $(,)?];)?>)*] => {{
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut key_up = KeyMappingEntries::key_array();
|
||||
let mut key_down = KeyMappingEntries::key_array();
|
||||
let mut pointer_move: KeyMappingEntries = Default::default();
|
||||
let mut mouse_scroll: KeyMappingEntries = Default::default();
|
||||
$(
|
||||
for entry in $entry {
|
||||
let arr = match entry.trigger {
|
||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
};
|
||||
arr.push(entry.clone());
|
||||
}
|
||||
)*
|
||||
(key_up, key_down, pointer_move, mouse_scroll)
|
||||
}};
|
||||
pub struct Mapping {
|
||||
pub key_up: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pub key_down: [KeyMappingEntries; NUMBER_OF_KEYS],
|
||||
pub pointer_move: KeyMappingEntries,
|
||||
pub mouse_scroll: KeyMappingEntries,
|
||||
}
|
||||
|
||||
impl Default for Mapping {
|
||||
fn default() -> Self {
|
||||
use input_mapper_macros::{entry, mapping, modifiers};
|
||||
use Key::*;
|
||||
|
||||
// WARNING!
|
||||
// If a new mapping isn't being handled (and perhaps another lower-precedence one is instead), make sure to advertise
|
||||
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`)
|
||||
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`).
|
||||
|
||||
let mappings = mapping![
|
||||
// Higher priority than entries in sections below
|
||||
entry! {action=PortfolioMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Paste(Clipboard::User), key_down=KeyV, modifiers=[KeyControl]},
|
||||
// Transform layers
|
||||
entry! {action=TransformLayerMessage::ApplyOperation, key_down=KeyEnter},
|
||||
entry! {action=TransformLayerMessage::ApplyOperation, key_down=Lmb},
|
||||
entry! {action=TransformLayerMessage::CancelOperation, key_down=KeyEscape},
|
||||
entry! {action=TransformLayerMessage::CancelOperation, key_down=Rmb},
|
||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=KeyEnter},
|
||||
entry! {action=TransformLayerMessage::ApplyTransformOperation, key_down=Lmb},
|
||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=KeyEscape},
|
||||
entry! {action=TransformLayerMessage::CancelTransformOperation, key_down=Rmb},
|
||||
entry! {action=TransformLayerMessage::ConstrainX, key_down=KeyX},
|
||||
entry! {action=TransformLayerMessage::ConstrainY, key_down=KeyY},
|
||||
entry! {action=TransformLayerMessage::TypeBackspace, key_down=KeyBackspace},
|
||||
|
@ -222,7 +114,7 @@ impl Default for Mapping {
|
|||
// Editor Actions
|
||||
entry! {action=FrontendMessage::TriggerFileUpload, key_down=KeyO, modifiers=[KeyControl]},
|
||||
// Document Actions
|
||||
entry! {action=PortfolioMessage::Paste(User), key_down=KeyV, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Paste(Clipboard::User), key_down=KeyV, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::Redo, key_down=KeyZ, modifiers=[KeyControl, KeyShift]},
|
||||
entry! {action=DocumentMessage::Undo, key_down=KeyZ, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DeselectAllLayers, key_down=KeyA, modifiers=[KeyControl, KeyAlt]},
|
||||
|
@ -267,8 +159,8 @@ impl Default for Mapping {
|
|||
entry! {action=PortfolioMessage::CloseAllDocumentsWithConfirmation, key_down=KeyW, modifiers=[KeyControl, KeyAlt]},
|
||||
entry! {action=PortfolioMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Copy(User), key_down=KeyC, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Cut(User), key_down=KeyX, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Copy(Clipboard::User), key_down=KeyC, modifiers=[KeyControl]},
|
||||
entry! {action=PortfolioMessage::Cut(Clipboard::User), key_down=KeyX, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::UngroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl, KeyShift]},
|
||||
// Nudging
|
||||
|
@ -337,7 +229,7 @@ impl Default for Mapping {
|
|||
}
|
||||
|
||||
impl Mapping {
|
||||
fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
pub fn match_message(&self, message: InputMapperMessage, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
use InputMapperMessage::*;
|
||||
let list = match message {
|
||||
KeyDown(key) => &self.key_down[key as usize],
|
||||
|
@ -349,40 +241,103 @@ impl Mapping {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputMapper {
|
||||
mapping: Mapping,
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct MappingEntry {
|
||||
pub trigger: InputMapperMessage,
|
||||
pub modifiers: KeyStates,
|
||||
pub action: Message,
|
||||
}
|
||||
|
||||
impl InputMapper {
|
||||
pub fn hints(&self, actions: ActionList) -> String {
|
||||
let mut output = String::new();
|
||||
let mut actions = actions
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Global(_)));
|
||||
self.mapping
|
||||
.key_down
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, m)| {
|
||||
let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant())));
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KeyMappingEntries(pub Vec<MappingEntry>);
|
||||
|
||||
ma.map(|a| unsafe { (std::mem::transmute_copy::<usize, Key>(&i), a) })
|
||||
})
|
||||
.for_each(|(k, a)| {
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
|
||||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessor, ActionList)> for InputMapper {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessor, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, actions) = data;
|
||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
||||
responses.push_back(message);
|
||||
impl KeyMappingEntries {
|
||||
fn match_mapping(&self, keys: &KeyStates, actions: ActionList) -> Option<Message> {
|
||||
for entry in self.0.iter() {
|
||||
let all_required_modifiers_pressed = ((*keys & entry.modifiers) ^ entry.modifiers).is_empty();
|
||||
if all_required_modifiers_pressed && actions.iter().flatten().any(|action| entry.action.to_discriminant() == *action) {
|
||||
return Some(entry.action.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn push(&mut self, entry: MappingEntry) {
|
||||
self.0.push(entry)
|
||||
}
|
||||
|
||||
const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
fn key_array() -> [Self; NUMBER_OF_KEYS] {
|
||||
const DEFAULT: KeyMappingEntries = KeyMappingEntries::new();
|
||||
[DEFAULT; NUMBER_OF_KEYS]
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl Default for KeyMappingEntries {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
mod input_mapper_macros {
|
||||
macro_rules! modifiers {
|
||||
($($m:ident),*) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut state = KeyStates::new();
|
||||
$(
|
||||
state.set(Key::$m as usize);
|
||||
)*
|
||||
state
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! entry {
|
||||
{action=$action:expr, key_down=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyDown(Key::$key) $(, modifiers=[$($m),*])?}
|
||||
}};
|
||||
{action=$action:expr, key_up=$key:ident $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
entry!{action=$action, message=InputMapperMessage::KeyUp(Key::$key) $(, modifiers=[$($m),* ])?}
|
||||
}};
|
||||
{action=$action:expr, message=$message:expr $(, modifiers=[$($m:ident),* $(,)?])?} => {{
|
||||
&[MappingEntry {trigger: $message, modifiers: modifiers!($($($m),*)?), action: $action.into()}]
|
||||
}};
|
||||
{action=$action:expr, triggers=[$($m:ident),* $(,)?]} => {{
|
||||
&[
|
||||
MappingEntry {trigger:InputMapperMessage::PointerMove, action: $action.into(), modifiers: modifiers!()},
|
||||
$(
|
||||
MappingEntry {trigger:InputMapperMessage::KeyDown(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
MappingEntry {trigger:InputMapperMessage::KeyUp(Key::$m), action: $action.into(), modifiers: modifiers!()},
|
||||
)*
|
||||
]
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! mapping {
|
||||
//[$(<action=$action:expr; message=$key:expr; $(modifiers=[$($m:ident),* $(,)?];)?>)*] => {{
|
||||
[$($entry:expr),* $(,)?] => {{
|
||||
let mut key_up = KeyMappingEntries::key_array();
|
||||
let mut key_down = KeyMappingEntries::key_array();
|
||||
let mut pointer_move: KeyMappingEntries = Default::default();
|
||||
let mut mouse_scroll: KeyMappingEntries = Default::default();
|
||||
$(
|
||||
for entry in $entry {
|
||||
let arr = match entry.trigger {
|
||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||
};
|
||||
arr.push(entry.clone());
|
||||
}
|
||||
)*
|
||||
(key_up, key_down, pointer_move, mouse_scroll)
|
||||
}};
|
||||
}
|
||||
|
||||
pub(crate) use entry;
|
||||
pub(crate) use mapping;
|
||||
pub(crate) use modifiers;
|
||||
}
|
||||
|
|
16
editor/src/input/input_mapper_message.rs
Normal file
16
editor/src/input/input_mapper_message.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use super::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, InputMapper)]
|
||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||
pub enum InputMapperMessage {
|
||||
#[child]
|
||||
KeyDown(Key),
|
||||
#[child]
|
||||
KeyUp(Key),
|
||||
MouseScroll,
|
||||
PointerMove,
|
||||
}
|
44
editor/src/input/input_mapper_message_handler.rs
Normal file
44
editor/src/input/input_mapper_message_handler.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::input_mapper::Mapping;
|
||||
use super::keyboard::Key;
|
||||
use super::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputMapperMessageHandler {
|
||||
mapping: Mapping,
|
||||
}
|
||||
|
||||
impl InputMapperMessageHandler {
|
||||
pub fn hints(&self, actions: ActionList) -> String {
|
||||
let mut output = String::new();
|
||||
let mut actions = actions
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|a| !matches!(*a, MessageDiscriminant::Tool(ToolMessageDiscriminant::ActivateTool) | MessageDiscriminant::Global(_)));
|
||||
self.mapping
|
||||
.key_down
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, m)| {
|
||||
let ma = m.0.iter().find_map(|m| actions.find_map(|a| (a == m.action.to_discriminant()).then(|| m.action.to_discriminant())));
|
||||
|
||||
ma.map(|a| unsafe { (std::mem::transmute_copy::<usize, Key>(&i), a) })
|
||||
})
|
||||
.for_each(|(k, a)| {
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
|
||||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<InputMapperMessage, (&InputPreprocessorMessageHandler, ActionList)> for InputMapperMessageHandler {
|
||||
fn process_action(&mut self, message: InputMapperMessage, data: (&InputPreprocessorMessageHandler, ActionList), responses: &mut VecDeque<Message>) {
|
||||
let (input, actions) = data;
|
||||
if let Some(message) = self.mapping.match_message(message, &input.keyboard, actions) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
}
|
|
@ -1,25 +1,12 @@
|
|||
use std::usize;
|
||||
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{EditorMouseState, MouseKeys, MouseState, ViewportBounds};
|
||||
use crate::message_prelude::*;
|
||||
use bitflags::bitflags;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graphene::DocumentResponse;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, InputPreprocessor)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InputPreprocessorMessage {
|
||||
BoundsOfViewports(Vec<ViewportBounds>),
|
||||
KeyDown(Key, ModifierKeys),
|
||||
KeyUp(Key, ModifierKeys),
|
||||
MouseDown(EditorMouseState, ModifierKeys),
|
||||
MouseMove(EditorMouseState, ModifierKeys),
|
||||
MouseScroll(EditorMouseState, ModifierKeys),
|
||||
MouseUp(EditorMouseState, ModifierKeys),
|
||||
pub enum KeyPosition {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -32,167 +19,20 @@ bitflags! {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputPreprocessor {
|
||||
pub keyboard: KeyStates,
|
||||
pub mouse: MouseState,
|
||||
pub viewport_bounds: ViewportBounds,
|
||||
}
|
||||
|
||||
enum KeyPosition {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
InputPreprocessorMessage::BoundsOfViewports(bounds_of_viewports) => {
|
||||
assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported");
|
||||
|
||||
for bounds in bounds_of_viewports {
|
||||
let new_size = bounds.size();
|
||||
let existing_size = self.viewport_bounds.size();
|
||||
|
||||
let translation = (new_size - existing_size) / 2.;
|
||||
|
||||
// TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration
|
||||
self.viewport_bounds = bounds;
|
||||
|
||||
responses.push_back(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlay(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
DocumentMessage::Artboard(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
InputPreprocessorMessage::KeyDown(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.set(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::KeyUp(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Pressed) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
responses.push_back(InputMapperMessage::PointerMove.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
self.mouse.scroll_delta = mouse_state.scroll_delta;
|
||||
|
||||
responses.push_back(InputMapperMessage::MouseScroll.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Released) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// clean user input and if possible reconstruct it
|
||||
// store the changes in the keyboard if it is a key event
|
||||
// transform canvas coordinates to document coordinates
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl InputPreprocessor {
|
||||
fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Option<Message> {
|
||||
// Calculate the difference between the two key states (binary xor)
|
||||
let diff = self.mouse.mouse_keys ^ new_state.mouse_keys;
|
||||
self.mouse = new_state;
|
||||
let key = match diff {
|
||||
MouseKeys::LEFT => Key::Lmb,
|
||||
MouseKeys::RIGHT => Key::Rmb,
|
||||
MouseKeys::MIDDLE => Key::Mmb,
|
||||
MouseKeys::NONE => return None, // self.mouse.mouse_keys was invalid, e.g. when a drag began outside the client
|
||||
_ => {
|
||||
log::warn!("The number of buttons modified at the same time was greater than 1. Modification: {:#010b}", diff);
|
||||
Key::UnknownKey
|
||||
}
|
||||
};
|
||||
Some(match position {
|
||||
KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(),
|
||||
KeyPosition::Released => InputMapperMessage::KeyUp(key).into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
|
||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
||||
self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses);
|
||||
self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses);
|
||||
}
|
||||
|
||||
fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
|
||||
let key_was_down = self.keyboard.get(key as usize);
|
||||
if key_was_down && !key_is_down {
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
} else if !key_was_down && key_is_down {
|
||||
self.keyboard.set(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::input_preprocessor::ModifierKeys;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::mouse::{EditorMouseState, ViewportPosition};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::MessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use super::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[test]
|
||||
fn process_action_mouse_move_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
|
||||
let mut editor_mouse_state = EditorMouseState::new();
|
||||
editor_mouse_state.editor_position = ViewportPosition::new(4., 809.);
|
||||
let message = InputPreprocessorMessage::MouseMove(editor_mouse_state, ModifierKeys::ALT);
|
||||
|
@ -206,7 +46,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn process_action_mouse_down_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
|
||||
let message = InputPreprocessorMessage::MouseDown(EditorMouseState::new(), ModifierKeys::CONTROL);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
|
@ -218,7 +58,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn process_action_mouse_up_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
|
||||
let message = InputPreprocessorMessage::MouseUp(EditorMouseState::new(), ModifierKeys::SHIFT);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
|
@ -230,7 +70,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn process_action_key_down_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
|
||||
input_preprocessor.keyboard.set(Key::KeyControl as usize);
|
||||
let message = InputPreprocessorMessage::KeyDown(Key::KeyA, ModifierKeys::empty());
|
||||
let mut responses = VecDeque::new();
|
||||
|
@ -243,7 +83,7 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn process_action_key_up_handle_modifier_keys() {
|
||||
let mut input_preprocessor = InputPreprocessor::default();
|
||||
let mut input_preprocessor = InputPreprocessorMessageHandler::default();
|
||||
let message = InputPreprocessorMessage::KeyUp(Key::KeyS, ModifierKeys::CONTROL | ModifierKeys::SHIFT);
|
||||
let mut responses = VecDeque::new();
|
||||
|
||||
|
|
22
editor/src/input/input_preprocessor_message.rs
Normal file
22
editor/src/input/input_preprocessor_message.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use super::input_preprocessor::ModifierKeys;
|
||||
use super::keyboard::Key;
|
||||
use super::mouse::{EditorMouseState, ViewportBounds};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graphene::DocumentResponse;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, InputPreprocessor)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum InputPreprocessorMessage {
|
||||
BoundsOfViewports(Vec<ViewportBounds>),
|
||||
KeyDown(Key, ModifierKeys),
|
||||
KeyUp(Key, ModifierKeys),
|
||||
MouseDown(EditorMouseState, ModifierKeys),
|
||||
MouseMove(EditorMouseState, ModifierKeys),
|
||||
MouseScroll(EditorMouseState, ModifierKeys),
|
||||
MouseUp(EditorMouseState, ModifierKeys),
|
||||
}
|
159
editor/src/input/input_preprocessor_message_handler.rs
Normal file
159
editor/src/input/input_preprocessor_message_handler.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use super::input_preprocessor::{KeyPosition, ModifierKeys};
|
||||
use super::keyboard::{Key, KeyStates};
|
||||
use super::mouse::{MouseKeys, MouseState, ViewportBounds};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graphene::DocumentResponse;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InputPreprocessorMessageHandler {
|
||||
pub keyboard: KeyStates,
|
||||
pub mouse: MouseState,
|
||||
pub viewport_bounds: ViewportBounds,
|
||||
}
|
||||
|
||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessorMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
InputPreprocessorMessage::BoundsOfViewports(bounds_of_viewports) => {
|
||||
assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported");
|
||||
|
||||
for bounds in bounds_of_viewports {
|
||||
let new_size = bounds.size();
|
||||
let existing_size = self.viewport_bounds.size();
|
||||
|
||||
let translation = (new_size - existing_size) / 2.;
|
||||
|
||||
// TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration
|
||||
self.viewport_bounds = bounds;
|
||||
|
||||
responses.push_back(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlays(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
DocumentMessage::Artboard(
|
||||
graphene::Operation::TransformLayer {
|
||||
path: vec![],
|
||||
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
InputPreprocessorMessage::KeyDown(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.set(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::KeyUp(key, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseDown(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Pressed) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
InputPreprocessorMessage::MouseMove(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
responses.push_back(InputMapperMessage::PointerMove.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseScroll(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
self.mouse.scroll_delta = mouse_state.scroll_delta;
|
||||
|
||||
responses.push_back(InputMapperMessage::MouseScroll.into());
|
||||
}
|
||||
InputPreprocessorMessage::MouseUp(editor_mouse_state, modifier_keys) => {
|
||||
self.handle_modifier_keys(modifier_keys, responses);
|
||||
|
||||
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||
self.mouse.position = mouse_state.position;
|
||||
|
||||
if let Some(message) = self.translate_mouse_event(mouse_state, KeyPosition::Released) {
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Clean user input and if possible reconstruct it.
|
||||
// Store the changes in the keyboard if it is a key event.
|
||||
// Transform canvas coordinates to document coordinates.
|
||||
advertise_actions!();
|
||||
}
|
||||
|
||||
impl InputPreprocessorMessageHandler {
|
||||
fn translate_mouse_event(&mut self, new_state: MouseState, position: KeyPosition) -> Option<Message> {
|
||||
// Calculate the difference between the two key states (binary xor)
|
||||
let difference = self.mouse.mouse_keys ^ new_state.mouse_keys;
|
||||
|
||||
self.mouse = new_state;
|
||||
|
||||
let key = match difference {
|
||||
MouseKeys::LEFT => Key::Lmb,
|
||||
MouseKeys::RIGHT => Key::Rmb,
|
||||
MouseKeys::MIDDLE => Key::Mmb,
|
||||
MouseKeys::NONE => return None, // self.mouse.mouse_keys was invalid, e.g. when a drag began outside the client
|
||||
_ => {
|
||||
log::warn!("The number of buttons modified at the same time was greater than 1. Modification: {:#010b}", difference);
|
||||
Key::UnknownKey
|
||||
}
|
||||
};
|
||||
|
||||
Some(match position {
|
||||
KeyPosition::Pressed => InputMapperMessage::KeyDown(key).into(),
|
||||
KeyPosition::Released => InputMapperMessage::KeyUp(key).into(),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_modifier_keys(&mut self, modifier_keys: ModifierKeys, responses: &mut VecDeque<Message>) {
|
||||
self.handle_modifier_key(Key::KeyControl, modifier_keys.contains(ModifierKeys::CONTROL), responses);
|
||||
self.handle_modifier_key(Key::KeyShift, modifier_keys.contains(ModifierKeys::SHIFT), responses);
|
||||
self.handle_modifier_key(Key::KeyAlt, modifier_keys.contains(ModifierKeys::ALT), responses);
|
||||
}
|
||||
|
||||
fn handle_modifier_key(&mut self, key: Key, key_is_down: bool, responses: &mut VecDeque<Message>) {
|
||||
let key_was_down = self.keyboard.get(key as usize);
|
||||
|
||||
if key_was_down && !key_is_down {
|
||||
self.keyboard.unset(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyUp(key).into());
|
||||
} else if !key_was_down && key_is_down {
|
||||
self.keyboard.set(key as usize);
|
||||
responses.push_back(InputMapperMessage::KeyDown(key).into());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,18 @@
|
|||
use crate::message_prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
// Edit this to specify the storage type used
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
|
||||
|
||||
// TODO: Increase size of type
|
||||
/// Edit this to specify the storage type used.
|
||||
pub type StorageType = u128;
|
||||
|
||||
// base 2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type
|
||||
// Base-2 logarithm of the storage type used to represents how many bits you need to fully address every bit in that storage type
|
||||
const STORAGE_SIZE: u32 = (std::mem::size_of::<StorageType>() * 8).trailing_zeros();
|
||||
const STORAGE_SIZE_BITS: usize = 1 << STORAGE_SIZE;
|
||||
const KEY_MASK_STORAGE_LENGTH: usize = (NUMBER_OF_KEYS + STORAGE_SIZE_BITS - 1) >> STORAGE_SIZE;
|
||||
|
||||
pub type KeyStates = BitVector<KEY_MASK_STORAGE_LENGTH>;
|
||||
|
||||
#[impl_message(Message, InputMapperMessage, KeyDown)]
|
||||
|
@ -84,10 +87,12 @@ pub enum Key {
|
|||
KeyComma,
|
||||
KeyPeriod,
|
||||
|
||||
// This has to be the last element in the enum.
|
||||
// This has to be the last element in the enum
|
||||
NumKeys,
|
||||
}
|
||||
|
||||
pub const NUMBER_OF_KEYS: usize = Key::NumKeys as usize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum MouseMotion {
|
||||
None,
|
||||
|
@ -105,12 +110,6 @@ pub enum MouseMotion {
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BitVector<const LENGTH: usize>([StorageType; LENGTH]);
|
||||
|
||||
use std::{
|
||||
fmt::{Display, Formatter},
|
||||
ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign},
|
||||
usize,
|
||||
};
|
||||
|
||||
impl<const LENGTH: usize> BitVector<LENGTH> {
|
||||
#[inline]
|
||||
fn convert_index(bitvector_index: usize) -> (usize, StorageType) {
|
||||
|
@ -118,37 +117,48 @@ impl<const LENGTH: usize> BitVector<LENGTH> {
|
|||
let offset = bitvector_index >> STORAGE_SIZE;
|
||||
(offset, bit)
|
||||
}
|
||||
|
||||
pub const fn new() -> Self {
|
||||
Self([0; LENGTH])
|
||||
}
|
||||
|
||||
pub fn set(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] |= bit;
|
||||
}
|
||||
|
||||
pub fn unset(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] &= !bit;
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self, bitvector_index: usize) {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
self.0[offset] ^= bit;
|
||||
}
|
||||
|
||||
pub fn get(&self, bitvector_index: usize) -> bool {
|
||||
let (offset, bit) = Self::convert_index(bitvector_index);
|
||||
(self.0[offset] & bit) != 0
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let mut result = 0;
|
||||
|
||||
for storage in self.0.iter() {
|
||||
result |= storage;
|
||||
}
|
||||
|
||||
result == 0
|
||||
}
|
||||
|
||||
pub fn ones(&self) -> u32 {
|
||||
let mut result = 0;
|
||||
|
||||
for storage in self.0.iter() {
|
||||
result += storage.count_ones();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +174,7 @@ impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
|||
for storage in self.0.iter().rev() {
|
||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -181,6 +192,7 @@ macro_rules! bit_ops {
|
|||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<const LENGTH: usize> $op for &BitVector<LENGTH> {
|
||||
type Output = BitVector<LENGTH>;
|
||||
fn $func(self, right: Self) -> Self::Output {
|
||||
|
|
|
@ -3,7 +3,17 @@ pub mod input_preprocessor;
|
|||
pub mod keyboard;
|
||||
pub mod mouse;
|
||||
|
||||
pub use {
|
||||
input_mapper::{InputMapper, InputMapperMessage, InputMapperMessageDiscriminant},
|
||||
input_preprocessor::{InputPreprocessor, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant, ModifierKeys},
|
||||
};
|
||||
mod input_mapper_message;
|
||||
mod input_mapper_message_handler;
|
||||
mod input_preprocessor_message;
|
||||
mod input_preprocessor_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use input_mapper_message_handler::InputMapperMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use input_preprocessor_message_handler::InputPreprocessorMessageHandler;
|
||||
|
|
|
@ -36,13 +36,16 @@ pub struct ScrollDelta {
|
|||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
impl ScrollDelta {
|
||||
pub fn new(x: i32, y: i32, z: i32) -> ScrollDelta {
|
||||
ScrollDelta { x, y, z }
|
||||
}
|
||||
|
||||
pub fn as_dvec2(&self) -> DVec2 {
|
||||
DVec2::new(self.x as f64, self.y as f64)
|
||||
}
|
||||
|
||||
pub fn scroll_delta(&self) -> f64 {
|
||||
let (dx, dy) = (self.x, self.y);
|
||||
dy.signum() as f64 * ((dy * dy + i32::min(dy.abs(), dx.abs()).pow(2)) as f64).sqrt()
|
||||
|
@ -70,7 +73,8 @@ impl MouseState {
|
|||
}
|
||||
|
||||
pub fn from_keys_and_editor_position(keys: u8, position: ViewportPosition) -> Self {
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys");
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("Invalid modifier keys");
|
||||
|
||||
Self {
|
||||
position,
|
||||
mouse_keys,
|
||||
|
@ -100,7 +104,8 @@ impl EditorMouseState {
|
|||
}
|
||||
|
||||
pub fn from_keys_and_editor_position(keys: u8, editor_position: EditorPosition) -> Self {
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("invalid modifier keys");
|
||||
let mouse_keys = MouseKeys::from_bits(keys).expect("Invalid modifier keys");
|
||||
|
||||
Self {
|
||||
editor_position,
|
||||
mouse_keys,
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
// Since our policy is tabs, we want to stop clippy from warning about that
|
||||
#![allow(clippy::tabs_in_doc_comments)]
|
||||
|
||||
extern crate graphite_proc_macros;
|
||||
|
||||
pub mod communication;
|
||||
#[macro_use]
|
||||
pub mod misc;
|
||||
pub mod consts;
|
||||
mod document;
|
||||
mod frontend;
|
||||
mod global;
|
||||
pub mod document;
|
||||
pub mod frontend;
|
||||
pub mod global;
|
||||
pub mod input;
|
||||
pub mod tool;
|
||||
pub mod viewport_tools;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graphene::color::Color;
|
||||
|
@ -56,30 +53,35 @@ impl Default for Editor {
|
|||
pub mod message_prelude {
|
||||
pub use crate::communication::generate_uuid;
|
||||
pub use crate::communication::message::{AsMessage, Message, MessageDiscriminant};
|
||||
pub use crate::communication::{ActionList, MessageHandler};
|
||||
pub use crate::document::Clipboard;
|
||||
pub use crate::communication::message_handler::{ActionList, MessageHandler};
|
||||
|
||||
pub use crate::document::clipboards::Clipboard;
|
||||
pub use crate::LayerId;
|
||||
|
||||
pub use crate::document::{ArtboardMessage, ArtboardMessageDiscriminant};
|
||||
pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant};
|
||||
pub use crate::document::{MovementMessage, MovementMessageDiscriminant};
|
||||
pub use crate::document::{OverlayMessage, OverlayMessageDiscriminant};
|
||||
pub use crate::document::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||
pub use crate::document::{PortfolioMessage, PortfolioMessageDiscriminant};
|
||||
pub use crate::document::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
pub use crate::global::{GlobalMessage, GlobalMessageDiscriminant};
|
||||
pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||
pub use crate::misc::derivable_custom_traits::{ToDiscriminant, TransitiveChild};
|
||||
pub use crate::tool::tool_messages::*;
|
||||
pub use crate::tool::tools::crop::{CropMessage, CropMessageDiscriminant};
|
||||
pub use crate::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
|
||||
pub use crate::tool::tools::fill::{FillMessage, FillMessageDiscriminant};
|
||||
pub use crate::tool::tools::line::{LineMessage, LineMessageDiscriminant};
|
||||
pub use crate::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
|
||||
pub use crate::tool::tools::path::{PathMessage, PathMessageDiscriminant};
|
||||
pub use crate::tool::tools::pen::{PenMessage, PenMessageDiscriminant};
|
||||
pub use crate::tool::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant};
|
||||
pub use crate::tool::tools::select::{SelectMessage, SelectMessageDiscriminant};
|
||||
pub use crate::tool::tools::shape::{ShapeMessage, ShapeMessageDiscriminant};
|
||||
pub use crate::LayerId;
|
||||
pub use crate::viewport_tools::tool_message::{ToolMessage, ToolMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::crop::{CropMessage, CropMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::ellipse::{EllipseMessage, EllipseMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::fill::{FillMessage, FillMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::line::{LineMessage, LineMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::path::{PathMessage, PathMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::pen::{PenMessage, PenMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::select::{SelectMessage, SelectMessageDiscriminant};
|
||||
pub use crate::viewport_tools::tools::shape::{ShapeMessage, ShapeMessageDiscriminant};
|
||||
|
||||
pub use graphite_proc_macros::*;
|
||||
|
||||
pub use std::collections::VecDeque;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::Color;
|
||||
use graphene::color::Color;
|
||||
use graphene::DocumentError;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// The error type used by the Graphite editor.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HintData(pub Vec<HintGroup>);
|
||||
|
||||
|
@ -13,8 +13,10 @@ pub struct HintInfo {
|
|||
pub key_groups: Vec<KeysGroup>,
|
||||
pub mouse: Option<MouseMotion>,
|
||||
pub label: String,
|
||||
pub plus: bool, // Prepend the "+" symbol indicating that this is a refinement upon a previous entry in the group
|
||||
/// Prepend the "+" symbol indicating that this is a refinement upon a previous entry in the group.
|
||||
pub plus: bool,
|
||||
}
|
||||
|
||||
/// Only `Key`s that exist on a physical keyboard should be used.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct KeysGroup(pub Vec<Key>); // Only use `Key`s that exist on a physical keyboard
|
||||
pub struct KeysGroup(pub Vec<Key>);
|
||||
|
|
|
@ -24,25 +24,25 @@ macro_rules! count_args {
|
|||
///
|
||||
/// ```ignore
|
||||
/// let tools = gen_tools_hash_map! {
|
||||
/// Select => select::Select,
|
||||
/// Crop => crop::Crop,
|
||||
/// Select => select::Select,
|
||||
/// Crop => crop::Crop,
|
||||
/// };
|
||||
/// ```
|
||||
/// expands to
|
||||
/// ```ignore
|
||||
/// let tools = {
|
||||
/// let mut hash_map: std::collections::HashMap<crate::tool::ToolType, Box<dyn crate::tool::Tool>> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */));
|
||||
/// let mut hash_map: std::collections::HashMap<crate::tool::ToolType, Box<dyn crate::tool::Tool>> = std::collections::HashMap::with_capacity(count_args!(/* Macro args */));
|
||||
///
|
||||
/// hash_map.insert(crate::tool::ToolType::Select, Box::new(select::Select::default()));
|
||||
/// hash_map.insert(crate::tool::ToolType::Crop, Box::new(crop::Crop::default()));
|
||||
/// hash_map.insert(crate::tool::ToolType::Select, Box::new(select::Select::default()));
|
||||
/// hash_map.insert(crate::tool::ToolType::Crop, Box::new(crop::Crop::default()));
|
||||
///
|
||||
/// hash_map
|
||||
/// hash_map
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! gen_tools_hash_map {
|
||||
($($enum_variant:ident => $struct_path:ty),* $(,)?) => {{
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::tool::ToolType, ::std::boxed::Box<dyn for<'a> $crate::message_prelude::MessageHandler<$crate::tool::tool_messages::ToolMessage,$crate::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
$(hash_map.insert($crate::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)*
|
||||
let mut hash_map: ::std::collections::HashMap<$crate::viewport_tools::tool::ToolType, ::std::boxed::Box<dyn for<'a> $crate::message_prelude::MessageHandler<$crate::viewport_tools::tool_message::ToolMessage,$crate::viewport_tools::tool::ToolActionHandlerData<'a>>>> = ::std::collections::HashMap::with_capacity(count_args!($(($enum_variant)),*));
|
||||
$(hash_map.insert($crate::viewport_tools::tool::ToolType::$enum_variant, ::std::boxed::Box::new(<$struct_path>::default()));)*
|
||||
|
||||
hash_map
|
||||
}};
|
||||
|
@ -54,8 +54,8 @@ macro_rules! gen_tools_hash_map {
|
|||
///
|
||||
/// ```ignore
|
||||
/// enum E {
|
||||
/// A(u8),
|
||||
/// B
|
||||
/// A(u8),
|
||||
/// B
|
||||
/// }
|
||||
///
|
||||
/// // this line is important
|
||||
|
@ -71,8 +71,8 @@ macro_rules! gen_tools_hash_map {
|
|||
/// // ...
|
||||
///
|
||||
/// let s = match a {
|
||||
/// A { .. } => "A",
|
||||
/// B { .. } => "B"
|
||||
/// A { .. } => "A",
|
||||
/// B { .. } => "B"
|
||||
/// };
|
||||
/// ```
|
||||
macro_rules! match_variant_name {
|
||||
|
@ -110,10 +110,11 @@ macro_rules! match_variant_name {
|
|||
///
|
||||
macro_rules! actions {
|
||||
($($v:expr),* $(,)?) => {{
|
||||
vec![$(vec![$v.into()]),*]
|
||||
vec![$(vec![$v.into()]),*]
|
||||
}};
|
||||
|
||||
($name:ident; $($v:ident),* $(,)?) => {{
|
||||
vec![vec![$(($name::$v).into()),*]]
|
||||
vec![vec![$(($name::$v).into()),*]]
|
||||
}};
|
||||
}
|
||||
|
||||
|
@ -121,18 +122,19 @@ macro_rules! actions {
|
|||
///
|
||||
/// ```ignore
|
||||
/// fn actions(&self) -> ActionList {
|
||||
/// actions!(…)
|
||||
/// actions!(…)
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! advertise_actions {
|
||||
($($v:expr),* $(,)?) => {
|
||||
fn actions(&self) -> $crate::communication::ActionList {
|
||||
actions!($($v),*)
|
||||
fn actions(&self) -> $crate::communication::message_handler::ActionList {
|
||||
actions!($($v),*)
|
||||
}
|
||||
};
|
||||
|
||||
($name:ident; $($v:ident),* $(,)?) => {
|
||||
fn actions(&self) -> $crate::communication::ActionList {
|
||||
actions!($name; $($v),*)
|
||||
fn actions(&self) -> $crate::communication::message_handler::ActionList {
|
||||
actions!($name; $($v),*)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
#[macro_use]
|
||||
pub mod macros;
|
||||
|
||||
pub mod derivable_custom_traits;
|
||||
mod error;
|
||||
pub mod hints;
|
||||
pub mod test_utils;
|
||||
|
||||
pub use error::EditorError;
|
||||
pub use hints::*;
|
||||
pub use macros::*;
|
||||
|
||||
mod error;
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
use crate::{
|
||||
input::{
|
||||
mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition},
|
||||
InputPreprocessorMessage, ModifierKeys,
|
||||
},
|
||||
message_prelude::{Message, ToolMessage},
|
||||
tool::ToolType,
|
||||
Editor,
|
||||
};
|
||||
use crate::input::input_preprocessor::ModifierKeys;
|
||||
use crate::input::mouse::{EditorMouseState, MouseKeys, ScrollDelta, ViewportPosition};
|
||||
use crate::message_prelude::*;
|
||||
use crate::viewport_tools::tool::ToolType;
|
||||
use crate::Editor;
|
||||
|
||||
use graphene::color::Color;
|
||||
|
||||
/// A set of utility functions to make the writing of editor test more declarative
|
||||
|
|
|
@ -1,204 +0,0 @@
|
|||
mod snapping;
|
||||
pub mod tool_message_handler;
|
||||
pub mod tool_options;
|
||||
pub mod tools;
|
||||
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use crate::{
|
||||
communication::{message::Message, MessageHandler},
|
||||
Color,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, Debug},
|
||||
};
|
||||
pub use tool_message_handler::ToolMessageHandler;
|
||||
use tool_options::ToolOptions;
|
||||
pub use tool_options::*;
|
||||
use tools::*;
|
||||
|
||||
pub mod tool_messages {
|
||||
pub use super::tool_message_handler::{ToolMessage, ToolMessageDiscriminant};
|
||||
pub use super::tools::ellipse::{EllipseMessage, EllipseMessageDiscriminant};
|
||||
pub use super::tools::rectangle::{RectangleMessage, RectangleMessageDiscriminant};
|
||||
}
|
||||
|
||||
pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessor);
|
||||
|
||||
pub trait Fsm {
|
||||
type ToolData;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
message: ToolMessage,
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self;
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentToolData {
|
||||
pub primary_color: Color,
|
||||
pub secondary_color: Color,
|
||||
pub tool_options: HashMap<ToolType, ToolOptions>,
|
||||
}
|
||||
|
||||
type SubToolMessageHandler = dyn for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>>;
|
||||
pub struct ToolData {
|
||||
pub active_tool_type: ToolType,
|
||||
pub tools: HashMap<ToolType, Box<SubToolMessageHandler>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ToolData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ToolData").field("active_tool_type", &self.active_tool_type).field("tool_options", &"[…]").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolData {
|
||||
pub fn active_tool_mut(&mut self) -> &mut Box<SubToolMessageHandler> {
|
||||
self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized")
|
||||
}
|
||||
pub fn active_tool(&self) -> &SubToolMessageHandler {
|
||||
self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolFsmState {
|
||||
pub document_tool_data: DocumentToolData,
|
||||
pub tool_data: ToolData,
|
||||
}
|
||||
|
||||
impl Default for ToolFsmState {
|
||||
fn default() -> Self {
|
||||
ToolFsmState {
|
||||
tool_data: ToolData {
|
||||
active_tool_type: ToolType::Select,
|
||||
tools: gen_tools_hash_map! {
|
||||
Rectangle => rectangle::Rectangle,
|
||||
Select => select::Select,
|
||||
Crop => crop::Crop,
|
||||
Navigate => navigate::Navigate,
|
||||
Eyedropper => eyedropper::Eyedropper,
|
||||
Path => path::Path,
|
||||
Pen => pen::Pen,
|
||||
Line => line::Line,
|
||||
Shape => shape::Shape,
|
||||
Ellipse => ellipse::Ellipse,
|
||||
Fill => fill::Fill,
|
||||
},
|
||||
},
|
||||
document_tool_data: DocumentToolData {
|
||||
primary_color: Color::BLACK,
|
||||
secondary_color: Color::WHITE,
|
||||
tool_options: default_tool_options(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolFsmState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn swap_colors(&mut self) {
|
||||
std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
|
||||
let tool_init = |tool: ToolType| (tool, tool.default_options());
|
||||
[
|
||||
tool_init(ToolType::Select),
|
||||
tool_init(ToolType::Pen),
|
||||
tool_init(ToolType::Line),
|
||||
tool_init(ToolType::Ellipse),
|
||||
tool_init(ToolType::Shape), // TODO: Add more tool defaults
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolType {
|
||||
Select,
|
||||
Crop,
|
||||
Navigate,
|
||||
Eyedropper,
|
||||
Text,
|
||||
Fill,
|
||||
Gradient,
|
||||
Brush,
|
||||
Heal,
|
||||
Clone,
|
||||
Patch,
|
||||
BlurSharpen,
|
||||
Relight,
|
||||
Path,
|
||||
Pen,
|
||||
Freehand,
|
||||
Spline,
|
||||
Line,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
Shape,
|
||||
}
|
||||
|
||||
impl fmt::Display for ToolType {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ToolType::*;
|
||||
|
||||
let name = match_variant_name!(match (self) {
|
||||
Select,
|
||||
Crop,
|
||||
Navigate,
|
||||
Eyedropper,
|
||||
Text,
|
||||
Fill,
|
||||
Gradient,
|
||||
Brush,
|
||||
Heal,
|
||||
Clone,
|
||||
Patch,
|
||||
BlurSharpen,
|
||||
Relight,
|
||||
Path,
|
||||
Pen,
|
||||
Freehand,
|
||||
Spline,
|
||||
Line,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
Shape
|
||||
});
|
||||
|
||||
formatter.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolType {
|
||||
fn default_options(&self) -> ToolOptions {
|
||||
match self {
|
||||
ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New },
|
||||
ToolType::Pen => ToolOptions::Pen { weight: 5 },
|
||||
ToolType::Line => ToolOptions::Line { weight: 5 },
|
||||
ToolType::Ellipse => ToolOptions::Ellipse {},
|
||||
ToolType::Shape => ToolOptions::Shape {
|
||||
shape_type: ShapeType::Polygon { vertices: 6 },
|
||||
},
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
6
editor/src/viewport_tools/mod.rs
Normal file
6
editor/src/viewport_tools/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
pub mod snapping;
|
||||
pub mod tool;
|
||||
pub mod tool_message;
|
||||
pub mod tool_message_handler;
|
||||
pub mod tool_options;
|
||||
pub mod tools;
|
|
@ -1,9 +1,9 @@
|
|||
use glam::DVec2;
|
||||
use crate::consts::SNAP_TOLERANCE;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
|
||||
use graphene::LayerId;
|
||||
|
||||
use crate::consts::SNAP_TOLERANCE;
|
||||
|
||||
use super::DocumentMessageHandler;
|
||||
use glam::DVec2;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SnapHandler {
|
||||
|
@ -99,6 +99,7 @@ impl SnapHandler {
|
|||
}
|
||||
}
|
||||
|
||||
/// Removes snap target data. Call this when snapping is done.
|
||||
pub fn cleanup(&mut self) {
|
||||
self.snap_targets = None;
|
||||
}
|
316
editor/src/viewport_tools/tool.rs
Normal file
316
editor/src/viewport_tools/tool.rs
Normal file
|
@ -0,0 +1,316 @@
|
|||
use super::tool_options::{SelectAppendMode, ShapeType, ToolOptions};
|
||||
use super::tools::*;
|
||||
use crate::communication::message_handler::MessageHandler;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, &'a DocumentToolData, &'a InputPreprocessorMessageHandler);
|
||||
|
||||
pub trait Fsm {
|
||||
type ToolData;
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
message: ToolMessage,
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self;
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DocumentToolData {
|
||||
pub primary_color: Color,
|
||||
pub secondary_color: Color,
|
||||
pub tool_options: HashMap<ToolType, ToolOptions>,
|
||||
}
|
||||
|
||||
type SubToolMessageHandler = dyn for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>>;
|
||||
|
||||
pub struct ToolData {
|
||||
pub active_tool_type: ToolType,
|
||||
pub tools: HashMap<ToolType, Box<SubToolMessageHandler>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ToolData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ToolData").field("active_tool_type", &self.active_tool_type).field("tool_options", &"[…]").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolData {
|
||||
pub fn active_tool_mut(&mut self) -> &mut Box<SubToolMessageHandler> {
|
||||
self.tools.get_mut(&self.active_tool_type).expect("The active tool is not initialized")
|
||||
}
|
||||
pub fn active_tool(&self) -> &SubToolMessageHandler {
|
||||
self.tools.get(&self.active_tool_type).map(|x| x.as_ref()).expect("The active tool is not initialized")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToolFsmState {
|
||||
pub document_tool_data: DocumentToolData,
|
||||
pub tool_data: ToolData,
|
||||
}
|
||||
|
||||
impl Default for ToolFsmState {
|
||||
fn default() -> Self {
|
||||
ToolFsmState {
|
||||
tool_data: ToolData {
|
||||
active_tool_type: ToolType::Select,
|
||||
tools: gen_tools_hash_map! {
|
||||
Select => select::Select,
|
||||
Crop => crop::Crop,
|
||||
Navigate => navigate::Navigate,
|
||||
Eyedropper => eyedropper::Eyedropper,
|
||||
// Text => text::Text,
|
||||
Fill => fill::Fill,
|
||||
// Gradient => gradient::Gradient,
|
||||
// Brush => brush::Brush,
|
||||
// Heal => heal::Heal,
|
||||
// Clone => clone::Clone,
|
||||
// Patch => patch::Patch,
|
||||
// BlurSharpen => blursharpen::BlurSharpen,
|
||||
// Relight => relight::Relight,
|
||||
Path => path::Path,
|
||||
Pen => pen::Pen,
|
||||
// Freehand => freehand::Freehand,
|
||||
// Spline => spline::Spline,
|
||||
Line => line::Line,
|
||||
Rectangle => rectangle::Rectangle,
|
||||
Ellipse => ellipse::Ellipse,
|
||||
Shape => shape::Shape,
|
||||
},
|
||||
},
|
||||
document_tool_data: DocumentToolData {
|
||||
primary_color: Color::BLACK,
|
||||
secondary_color: Color::WHITE,
|
||||
tool_options: default_tool_options(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolFsmState {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn swap_colors(&mut self) {
|
||||
std::mem::swap(&mut self.document_tool_data.primary_color, &mut self.document_tool_data.secondary_color);
|
||||
}
|
||||
}
|
||||
|
||||
fn default_tool_options() -> HashMap<ToolType, ToolOptions> {
|
||||
let tool_init = |tool: ToolType| (tool, tool.default_options());
|
||||
[
|
||||
tool_init(ToolType::Select),
|
||||
tool_init(ToolType::Crop),
|
||||
tool_init(ToolType::Navigate),
|
||||
tool_init(ToolType::Eyedropper),
|
||||
tool_init(ToolType::Text),
|
||||
tool_init(ToolType::Fill),
|
||||
tool_init(ToolType::Gradient),
|
||||
tool_init(ToolType::Brush),
|
||||
tool_init(ToolType::Heal),
|
||||
tool_init(ToolType::Clone),
|
||||
tool_init(ToolType::Patch),
|
||||
tool_init(ToolType::BlurSharpen),
|
||||
tool_init(ToolType::Relight),
|
||||
tool_init(ToolType::Path),
|
||||
tool_init(ToolType::Pen),
|
||||
tool_init(ToolType::Freehand),
|
||||
tool_init(ToolType::Spline),
|
||||
tool_init(ToolType::Line),
|
||||
tool_init(ToolType::Rectangle),
|
||||
tool_init(ToolType::Ellipse),
|
||||
tool_init(ToolType::Shape),
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ToolType {
|
||||
Select,
|
||||
Crop,
|
||||
Navigate,
|
||||
Eyedropper,
|
||||
Text,
|
||||
Fill,
|
||||
Gradient,
|
||||
Brush,
|
||||
Heal,
|
||||
Clone,
|
||||
Patch,
|
||||
BlurSharpen,
|
||||
Relight,
|
||||
Path,
|
||||
Pen,
|
||||
Freehand,
|
||||
Spline,
|
||||
Line,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
Shape,
|
||||
}
|
||||
|
||||
impl fmt::Display for ToolType {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ToolType::*;
|
||||
|
||||
let name = match_variant_name!(match (self) {
|
||||
Select,
|
||||
Crop,
|
||||
Navigate,
|
||||
Eyedropper,
|
||||
Text,
|
||||
Fill,
|
||||
Gradient,
|
||||
Brush,
|
||||
Heal,
|
||||
Clone,
|
||||
Patch,
|
||||
BlurSharpen,
|
||||
Relight,
|
||||
Path,
|
||||
Pen,
|
||||
Freehand,
|
||||
Spline,
|
||||
Line,
|
||||
Rectangle,
|
||||
Ellipse,
|
||||
Shape
|
||||
});
|
||||
|
||||
formatter.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolType {
|
||||
fn default_options(&self) -> ToolOptions {
|
||||
match self {
|
||||
ToolType::Select => ToolOptions::Select { append_mode: SelectAppendMode::New },
|
||||
ToolType::Crop => ToolOptions::Crop {},
|
||||
ToolType::Navigate => ToolOptions::Navigate {},
|
||||
ToolType::Eyedropper => ToolOptions::Eyedropper {},
|
||||
ToolType::Text => ToolOptions::Text {},
|
||||
ToolType::Fill => ToolOptions::Fill {},
|
||||
ToolType::Gradient => ToolOptions::Gradient {},
|
||||
ToolType::Brush => ToolOptions::Brush {},
|
||||
ToolType::Heal => ToolOptions::Heal {},
|
||||
ToolType::Clone => ToolOptions::Clone {},
|
||||
ToolType::Patch => ToolOptions::Patch {},
|
||||
ToolType::BlurSharpen => ToolOptions::BlurSharpen {},
|
||||
ToolType::Relight => ToolOptions::Relight {},
|
||||
ToolType::Path => ToolOptions::Path {},
|
||||
ToolType::Pen => ToolOptions::Pen { weight: 5 },
|
||||
ToolType::Freehand => ToolOptions::Freehand {},
|
||||
ToolType::Spline => ToolOptions::Spline {},
|
||||
ToolType::Line => ToolOptions::Line { weight: 5 },
|
||||
ToolType::Rectangle => ToolOptions::Rectangle {},
|
||||
ToolType::Ellipse => ToolOptions::Ellipse {},
|
||||
ToolType::Shape => ToolOptions::Shape {
|
||||
shape_type: ShapeType::Polygon { vertices: 6 },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum StandardToolMessageType {
|
||||
Abort,
|
||||
DocumentIsDirty,
|
||||
}
|
||||
|
||||
// TODO: Find a nicer way in Rust to make this generic so we don't have to manually map to enum variants
|
||||
pub fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) -> Option<ToolMessage> {
|
||||
match message_type {
|
||||
StandardToolMessageType::DocumentIsDirty => match tool {
|
||||
ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()),
|
||||
ToolType::Crop => None, // Some(CropMessage::DocumentIsDirty.into()),
|
||||
ToolType::Navigate => None, // Some(NavigateMessage::DocumentIsDirty.into()),
|
||||
ToolType::Eyedropper => None, // Some(EyedropperMessage::DocumentIsDirty.into()),
|
||||
ToolType::Text => None, // Some(TextMessage::DocumentIsDirty.into()),
|
||||
ToolType::Fill => None, // Some(FillMessage::DocumentIsDirty.into()),
|
||||
ToolType::Gradient => None, // Some(GradientMessage::DocumentIsDirty.into()),
|
||||
ToolType::Brush => None, // Some(BrushMessage::DocumentIsDirty.into()),
|
||||
ToolType::Heal => None, // Some(HealMessage::DocumentIsDirty.into()),
|
||||
ToolType::Clone => None, // Some(CloneMessage::DocumentIsDirty.into()),
|
||||
ToolType::Patch => None, // Some(PatchMessage::DocumentIsDirty.into()),
|
||||
ToolType::BlurSharpen => None, // Some(BlurSharpenMessage::DocumentIsDirty.into()),
|
||||
ToolType::Relight => None, // Some(RelightMessage::DocumentIsDirty.into()),
|
||||
ToolType::Path => Some(PathMessage::DocumentIsDirty.into()),
|
||||
ToolType::Pen => None, // Some(PenMessage::DocumentIsDirty.into()),
|
||||
ToolType::Freehand => None, // Some(FreehandMessage::DocumentIsDirty.into()),
|
||||
ToolType::Spline => None, // Some(SplineMessage::DocumentIsDirty.into()),
|
||||
ToolType::Line => None, // Some(LineMessage::DocumentIsDirty.into()),
|
||||
ToolType::Rectangle => None, // Some(RectangleMessage::DocumentIsDirty.into()),
|
||||
ToolType::Ellipse => None, // Some(EllipseMessage::DocumentIsDirty.into()),
|
||||
ToolType::Shape => None, // Some(ShapeMessage::DocumentIsDirty.into()),
|
||||
},
|
||||
StandardToolMessageType::Abort => match tool {
|
||||
ToolType::Select => Some(SelectMessage::Abort.into()),
|
||||
ToolType::Path => Some(PathMessage::Abort.into()),
|
||||
ToolType::Navigate => Some(NavigateMessage::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()),
|
||||
ToolType::Eyedropper => Some(EyedropperMessage::Abort.into()),
|
||||
ToolType::Fill => Some(FillMessage::Abort.into()),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn message_to_tool_type(message: &ToolMessage) -> ToolType {
|
||||
use ToolMessage::*;
|
||||
|
||||
match message {
|
||||
Select(_) => ToolType::Select,
|
||||
Crop(_) => ToolType::Crop,
|
||||
Navigate(_) => ToolType::Navigate,
|
||||
Eyedropper(_) => ToolType::Eyedropper,
|
||||
// Text(_) => ToolType::Text,
|
||||
Fill(_) => ToolType::Fill,
|
||||
// Gradient(_) => ToolType::Gradient,
|
||||
// Brush(_) => ToolType::Brush,
|
||||
// Heal(_) => ToolType::Heal,
|
||||
// Clone(_) => ToolType::Clone,
|
||||
// Patch(_) => ToolType::Patch,
|
||||
// BlurSharpen(_) => ToolType::BlurSharpen,
|
||||
// Relight(_) => ToolType::Relight,
|
||||
Path(_) => ToolType::Path,
|
||||
Pen(_) => ToolType::Pen,
|
||||
// Freehand(_) => ToolType::Freehand,
|
||||
// Spline(_) => ToolType::Spline,
|
||||
Line(_) => ToolType::Line,
|
||||
Rectangle(_) => ToolType::Rectangle,
|
||||
Ellipse(_) => ToolType::Ellipse,
|
||||
Shape(_) => ToolType::Shape,
|
||||
_ => panic!("Conversion from message to tool type impossible because the given ToolMessage does not belong to a tool"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateWorkingColors {
|
||||
primary: document_data.primary_color,
|
||||
secondary: document_data.secondary_color,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
44
editor/src/viewport_tools/tool_message.rs
Normal file
44
editor/src/viewport_tools/tool_message.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use super::tool::ToolType;
|
||||
use super::tool_options::ToolOptions;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Tool)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ToolMessage {
|
||||
ActivateTool(ToolType),
|
||||
#[child]
|
||||
Crop(CropMessage),
|
||||
DocumentIsDirty,
|
||||
#[child]
|
||||
Ellipse(EllipseMessage),
|
||||
#[child]
|
||||
Eyedropper(EyedropperMessage),
|
||||
#[child]
|
||||
Fill(FillMessage),
|
||||
#[child]
|
||||
Line(LineMessage),
|
||||
#[child]
|
||||
Navigate(NavigateMessage),
|
||||
NoOp,
|
||||
#[child]
|
||||
Path(PathMessage),
|
||||
#[child]
|
||||
Pen(PenMessage),
|
||||
#[child]
|
||||
Rectangle(RectangleMessage),
|
||||
ResetColors,
|
||||
#[child]
|
||||
Select(SelectMessage),
|
||||
SelectPrimaryColor(Color),
|
||||
SelectSecondaryColor(Color),
|
||||
SetToolOptions(ToolType, ToolOptions),
|
||||
#[child]
|
||||
Shape(ShapeMessage),
|
||||
SwapColors,
|
||||
UpdateHints,
|
||||
}
|
|
@ -1,61 +1,23 @@
|
|||
use super::tool::{message_to_tool_type, standard_tool_message, update_working_colors, StandardToolMessageType, ToolFsmState};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::color::Color;
|
||||
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::{
|
||||
document::DocumentMessageHandler,
|
||||
tool::{tool_options::ToolOptions, DocumentToolData, ToolFsmState, ToolType},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Tool)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ToolMessage {
|
||||
ActivateTool(ToolType),
|
||||
#[child]
|
||||
Crop(CropMessage),
|
||||
DocumentIsDirty,
|
||||
#[child]
|
||||
Ellipse(EllipseMessage),
|
||||
#[child]
|
||||
Eyedropper(EyedropperMessage),
|
||||
#[child]
|
||||
Fill(FillMessage),
|
||||
#[child]
|
||||
Line(LineMessage),
|
||||
#[child]
|
||||
Navigate(NavigateMessage),
|
||||
NoOp,
|
||||
#[child]
|
||||
Path(PathMessage),
|
||||
#[child]
|
||||
Pen(PenMessage),
|
||||
#[child]
|
||||
Rectangle(RectangleMessage),
|
||||
ResetColors,
|
||||
#[child]
|
||||
Select(SelectMessage),
|
||||
SelectPrimaryColor(Color),
|
||||
SelectSecondaryColor(Color),
|
||||
SetToolOptions(ToolType, ToolOptions),
|
||||
#[child]
|
||||
Shape(ShapeMessage),
|
||||
SwapColors,
|
||||
UpdateHints,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ToolMessageHandler {
|
||||
tool_state: ToolFsmState,
|
||||
}
|
||||
|
||||
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)> for ToolMessageHandler {
|
||||
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessorMessageHandler)> for ToolMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
let (document, input) = data;
|
||||
fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
use ToolMessage::*;
|
||||
|
||||
let (document, input) = data;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ActivateTool(new_tool) => {
|
||||
|
@ -159,69 +121,3 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||
list
|
||||
}
|
||||
}
|
||||
|
||||
enum StandardToolMessageType {
|
||||
Abort,
|
||||
DocumentIsDirty,
|
||||
}
|
||||
|
||||
// TODO: Find a nicer way in Rust to make this generic so we don't have to manually map to enum variants
|
||||
fn standard_tool_message(tool: ToolType, message_type: StandardToolMessageType) -> Option<ToolMessage> {
|
||||
match message_type {
|
||||
StandardToolMessageType::DocumentIsDirty => match tool {
|
||||
ToolType::Select => Some(SelectMessage::DocumentIsDirty.into()),
|
||||
ToolType::Path => Some(PathMessage::DocumentIsDirty.into()),
|
||||
//ToolType::Navigate => Some(NavigateMessage::DocumentIsDirty.into())
|
||||
// ToolType::Pen => Some(PenMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Line => Some(LineMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Rectangle => Some(RectangleMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Ellipse => Some(EllipseMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Shape => Some(ShapeMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Eyedropper => Some(EyedropperMessage::DocumentIsDirty.into()),
|
||||
// ToolType::Fill => Some(FillMessage::DocumentIsDirty.into()),
|
||||
_ => None,
|
||||
},
|
||||
StandardToolMessageType::Abort => match tool {
|
||||
ToolType::Select => Some(SelectMessage::Abort.into()),
|
||||
ToolType::Path => Some(PathMessage::Abort.into()),
|
||||
ToolType::Navigate => Some(NavigateMessage::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()),
|
||||
ToolType::Eyedropper => Some(EyedropperMessage::Abort.into()),
|
||||
ToolType::Fill => Some(FillMessage::Abort.into()),
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn message_to_tool_type(message: &ToolMessage) -> ToolType {
|
||||
use ToolMessage::*;
|
||||
|
||||
match message {
|
||||
Fill(_) => ToolType::Fill,
|
||||
Rectangle(_) => ToolType::Rectangle,
|
||||
Ellipse(_) => ToolType::Ellipse,
|
||||
Shape(_) => ToolType::Shape,
|
||||
Line(_) => ToolType::Line,
|
||||
Pen(_) => ToolType::Pen,
|
||||
Select(_) => ToolType::Select,
|
||||
Crop(_) => ToolType::Crop,
|
||||
Eyedropper(_) => ToolType::Eyedropper,
|
||||
Navigate(_) => ToolType::Navigate,
|
||||
Path(_) => ToolType::Path,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_working_colors(document_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateWorkingColors {
|
||||
primary: document_data.primary_color,
|
||||
secondary: document_data.secondary_color,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
|
@ -3,10 +3,26 @@ use serde::{Deserialize, Serialize};
|
|||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub enum ToolOptions {
|
||||
Select { append_mode: SelectAppendMode },
|
||||
Crop {},
|
||||
Navigate {},
|
||||
Eyedropper {},
|
||||
Text {},
|
||||
Fill {},
|
||||
Gradient {},
|
||||
Brush {},
|
||||
Heal {},
|
||||
Clone {},
|
||||
Patch {},
|
||||
BlurSharpen {},
|
||||
Relight {},
|
||||
Path {},
|
||||
Pen { weight: u32 },
|
||||
Freehand {},
|
||||
Spline {},
|
||||
Line { weight: u32 },
|
||||
Rectangle {},
|
||||
Ellipse {},
|
||||
Shape { shape_type: ShapeType },
|
||||
Line { weight: u32 },
|
||||
Pen { weight: u32 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
|
@ -1,5 +1,6 @@
|
|||
use crate::message_prelude::*;
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use crate::viewport_tools::tool::ToolActionHandlerData;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -16,5 +17,6 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Crop {
|
|||
fn process_action(&mut self, action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||
todo!("{}::handle_input {:?} {:?} {:?} ", module_path!(), action, data, responses);
|
||||
}
|
||||
|
||||
advertise_actions!();
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
use super::shared::resize::Resize;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::{keyboard::Key, keyboard::MouseMotion, InputPreprocessor};
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::{tools::resize::Resize, DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -73,7 +78,7 @@ impl Fsm for EllipseToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let mut shape_data = &mut data.data;
|
|
@ -1,12 +1,15 @@
|
|||
use crate::consts::SELECTION_TOLERANCE;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::{keyboard::MouseMotion, InputPreprocessor};
|
||||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolMessage};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
|
||||
use glam::DVec2;
|
||||
use graphene::layers::LayerDataType;
|
||||
use graphene::Quad;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -65,7 +68,7 @@ impl Fsm for EyedropperToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use EyedropperMessage::*;
|
||||
|
@ -77,6 +80,7 @@ impl Fsm for EyedropperToolFsmState {
|
|||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
let quad = Quad::from_box([mouse_pos - tolerance, mouse_pos + tolerance]);
|
||||
|
||||
// TODO: Destroy this pyramid
|
||||
if let Some(path) = document.graphene_document.intersects_quad_root(quad).last() {
|
||||
if let Ok(layer) = document.graphene_document.layer(path) {
|
||||
if let LayerDataType::Shape(shape) = &layer.data {
|
|
@ -1,12 +1,15 @@
|
|||
use crate::consts::SELECTION_TOLERANCE;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::{keyboard::MouseMotion, InputPreprocessor};
|
||||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo};
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolMessage};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::DVec2;
|
||||
use graphene::{Operation, Quad};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -65,7 +68,7 @@ impl Fsm for FillToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
_data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use FillMessage::*;
|
|
@ -1,12 +1,18 @@
|
|||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::snapping::SnapHandler;
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, ToolType};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -42,6 +48,7 @@ impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
|||
|
||||
fn actions(&self) -> ActionList {
|
||||
use LineToolFsmState::*;
|
||||
|
||||
match self.fsm_state {
|
||||
Ready => actions!(LineMessageDiscriminant; DragStart),
|
||||
Drawing => actions!(LineMessageDiscriminant; DragStop, Redraw, Abort),
|
||||
|
@ -60,6 +67,7 @@ impl Default for LineToolFsmState {
|
|||
LineToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct LineToolData {
|
||||
drag_start: ViewportPosition,
|
||||
|
@ -79,11 +87,12 @@ impl Fsm for LineToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use LineMessage::*;
|
||||
use LineToolFsmState::*;
|
||||
|
||||
if let ToolMessage::Line(event) = event {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
|
@ -1,15 +1,12 @@
|
|||
// already implemented
|
||||
pub mod crop;
|
||||
pub mod ellipse;
|
||||
pub mod eyedropper;
|
||||
pub mod fill;
|
||||
pub mod line;
|
||||
pub mod pen;
|
||||
pub mod rectangle;
|
||||
pub mod resize;
|
||||
pub mod shape;
|
||||
|
||||
// not implemented yet
|
||||
pub mod crop;
|
||||
pub mod eyedropper;
|
||||
pub mod navigate;
|
||||
pub mod path;
|
||||
pub mod pen;
|
||||
pub mod rectangle;
|
||||
pub mod select;
|
||||
pub mod shape;
|
||||
pub mod shared;
|
|
@ -1,7 +1,10 @@
|
|||
use crate::input::keyboard::MouseMotion;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::{Fsm, ToolActionHandlerData};
|
||||
use crate::{input::keyboard::Key, message_prelude::*};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -73,14 +76,15 @@ impl Fsm for NavigateToolFsmState {
|
|||
fn transition(
|
||||
self,
|
||||
message: ToolMessage,
|
||||
_document: &crate::document::DocumentMessageHandler,
|
||||
_tool_data: &crate::tool::DocumentToolData,
|
||||
_document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &crate::input::InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Navigate(navigate) = message {
|
||||
use NavigateMessage::*;
|
||||
|
||||
match navigate {
|
||||
ClickZoom { zoom_in } => {
|
||||
messages.push_front(MovementMessage::TransformCanvasEnd.into());
|
|
@ -1,24 +1,18 @@
|
|||
use crate::consts::COLOR_ACCENT;
|
||||
use crate::consts::VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE;
|
||||
use crate::consts::{COLOR_ACCENT, VECTOR_MANIPULATOR_ANCHOR_MARKER_SIZE};
|
||||
use crate::document::utility_types::{VectorManipulatorSegment, VectorManipulatorShape};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::document::VectorManipulatorSegment;
|
||||
use crate::document::VectorManipulatorShape;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::ToolActionHandlerData;
|
||||
use crate::tool::{DocumentToolData, Fsm};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::layers::style;
|
||||
use graphene::layers::style::Fill;
|
||||
use graphene::layers::style::PathStyle;
|
||||
use graphene::layers::style::Stroke;
|
||||
use graphene::layers::style::{self, Fill, PathStyle, Stroke};
|
||||
use graphene::Operation;
|
||||
use kurbo::BezPath;
|
||||
use kurbo::PathEl;
|
||||
use kurbo::Vec2;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, PathEl, Vec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -88,6 +82,7 @@ struct PathToolData {
|
|||
}
|
||||
|
||||
impl PathToolData {}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PathToolSelection {
|
||||
closest_layer_path: Vec<LayerId>,
|
||||
|
@ -106,12 +101,13 @@ impl Fsm for PathToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Path(event) = event {
|
||||
use PathMessage::*;
|
||||
use PathToolFsmState::*;
|
||||
|
||||
match (self, event) {
|
||||
(_, DocumentIsDirty) => {
|
||||
let (mut anchor_i, mut handle_i, mut line_i, mut shape_i) = (0, 0, 0, 0);
|
||||
|
@ -133,7 +129,7 @@ impl Fsm for PathToolFsmState {
|
|||
let shape_layer_path = &data.shape_outline_pool[shape_i];
|
||||
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetShapePathInViewport {
|
||||
path: shape_layer_path.clone(),
|
||||
bez_path: shape_to_draw.path.clone(),
|
||||
|
@ -144,7 +140,7 @@ impl Fsm for PathToolFsmState {
|
|||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetLayerVisibility {
|
||||
path: shape_layer_path.clone(),
|
||||
visible: true,
|
||||
|
@ -168,8 +164,8 @@ impl Fsm for PathToolFsmState {
|
|||
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(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
|
||||
line_i += 1;
|
||||
}
|
||||
|
@ -182,8 +178,8 @@ impl Fsm for PathToolFsmState {
|
|||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
let marker = &data.anchor_marker_pool[anchor_i];
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
|
||||
anchor_i += 1;
|
||||
}
|
||||
|
@ -197,8 +193,8 @@ impl Fsm for PathToolFsmState {
|
|||
let translation = (handle - (scale / 2.) + BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path: marker.clone(), transform }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker.clone(), visible: true }.into()).into());
|
||||
|
||||
handle_i += 1;
|
||||
}
|
||||
|
@ -207,19 +203,19 @@ impl Fsm for PathToolFsmState {
|
|||
// 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(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into());
|
||||
}
|
||||
for i in handle_i..data.handle_marker_pool.len() {
|
||||
let marker = data.handle_marker_pool[i].clone();
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: marker, visible: false }.into()).into());
|
||||
}
|
||||
for i in line_i..data.anchor_handle_line_pool.len() {
|
||||
let line = data.anchor_handle_line_pool[i].clone();
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: line, visible: false }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: line, visible: false }.into()).into());
|
||||
}
|
||||
for i in shape_i..data.shape_outline_pool.len() {
|
||||
let shape_i = data.shape_outline_pool[i].clone();
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::SetLayerVisibility { path: shape_i, visible: false }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerVisibility { path: shape_i, visible: false }.into()).into());
|
||||
}
|
||||
|
||||
self
|
||||
|
@ -335,7 +331,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
data.selection.overlay_path = path;
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetLayerFill {
|
||||
path: data.selection.overlay_path.clone(),
|
||||
color: COLOR_ACCENT,
|
||||
|
@ -377,7 +373,7 @@ impl Fsm for PathToolFsmState {
|
|||
(_, DragStop) => {
|
||||
let style = PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE)));
|
||||
responses.push_back(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetLayerStyle {
|
||||
path: data.selection.overlay_path.clone(),
|
||||
style,
|
||||
|
@ -391,16 +387,16 @@ impl Fsm for PathToolFsmState {
|
|||
(_, Abort) => {
|
||||
// Destory the overlay layer pools
|
||||
while let Some(layer) = data.anchor_marker_pool.pop() {
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
}
|
||||
while let Some(layer) = data.handle_marker_pool.pop() {
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
}
|
||||
while let Some(layer) = data.anchor_handle_line_pool.pop() {
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
}
|
||||
while let Some(layer) = data.shape_outline_pool.pop() {
|
||||
responses.push_back(DocumentMessage::Overlay(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::DeleteLayer { path: layer }.into()).into());
|
||||
}
|
||||
|
||||
Ready
|
||||
|
@ -573,7 +569,7 @@ fn add_anchor_marker(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlay(operation.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
layer_path
|
||||
}
|
||||
|
@ -586,7 +582,7 @@ fn add_handle_marker(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 2.0)), Some(Fill::new(Color::WHITE))),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlay(operation.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
layer_path
|
||||
}
|
||||
|
@ -598,7 +594,7 @@ fn add_anchor_handle_line(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlay(operation.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
layer_path
|
||||
}
|
||||
|
@ -612,7 +608,7 @@ fn add_shape_outline(responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
|||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||
closed: false,
|
||||
};
|
||||
responses.push_back(DocumentMessage::Overlay(operation.into()).into());
|
||||
responses.push_back(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
layer_path
|
||||
}
|
|
@ -1,11 +1,16 @@
|
|||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::snapping::SnapHandler;
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolOptions, ToolType};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::ToolOptions;
|
||||
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -80,7 +85,7 @@ impl Fsm for PenToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let transform = document.graphene_document.root.transform;
|
|
@ -1,13 +1,16 @@
|
|||
use super::shared::resize::Resize;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use super::resize::*;
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Rectangle {
|
||||
|
@ -74,7 +77,7 @@ impl Fsm for RectangleToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let mut shape_data = &mut data.data;
|
|
@ -1,22 +1,18 @@
|
|||
use graphene::layers::style;
|
||||
use graphene::layers::style::Fill;
|
||||
use graphene::layers::style::Stroke;
|
||||
use graphene::Operation;
|
||||
use graphene::Quad;
|
||||
|
||||
use crate::consts::COLOR_ACCENT;
|
||||
use crate::input::{
|
||||
keyboard::{Key, MouseMotion},
|
||||
mouse::ViewportPosition,
|
||||
InputPreprocessor,
|
||||
};
|
||||
use crate::consts::{COLOR_ACCENT, SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE};
|
||||
use crate::document::utility_types::{AlignAggregate, AlignAxis, FlipAxis};
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::{snapping::SnapHandler, DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
use crate::{
|
||||
consts::{SELECTION_DRAG_ANGLE, SELECTION_TOLERANCE},
|
||||
document::{AlignAggregate, AlignAxis, DocumentMessageHandler, FlipAxis},
|
||||
message_prelude::*,
|
||||
};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||
|
||||
use graphene::intersection::Quad;
|
||||
use graphene::layers::style::{self, Fill, Stroke};
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -115,7 +111,7 @@ fn add_bounding_box(responses: &mut Vec<Message>) -> Vec<LayerId> {
|
|||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, 1.0)), Some(Fill::none())),
|
||||
};
|
||||
responses.push(DocumentMessage::Overlay(operation.into()).into());
|
||||
responses.push(DocumentMessage::Overlays(operation.into()).into());
|
||||
|
||||
path
|
||||
}
|
||||
|
@ -133,7 +129,7 @@ impl Fsm for SelectToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
_tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
use SelectMessage::*;
|
||||
|
@ -144,7 +140,7 @@ impl Fsm for SelectToolFsmState {
|
|||
(_, DocumentIsDirty) => {
|
||||
let mut buffer = Vec::new();
|
||||
let response = match (document.selected_visible_layers_bounding_box(), data.bounding_box_overlay_layer.take()) {
|
||||
(None, Some(path)) => DocumentMessage::Overlay(Operation::DeleteLayer { path }.into()).into(),
|
||||
(None, Some(path)) => DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into(),
|
||||
(Some([pos1, pos2]), path) => {
|
||||
let path = path.unwrap_or_else(|| add_bounding_box(&mut buffer));
|
||||
|
||||
|
@ -154,7 +150,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let pos1 = pos1 + half_pixel_offset;
|
||||
let pos2 = pos2 - half_pixel_offset;
|
||||
let transform = transform_from_box(pos1, pos2);
|
||||
DocumentMessage::Overlay(Operation::SetLayerTransformInViewport { path, transform }.into()).into()
|
||||
DocumentMessage::Overlays(Operation::SetLayerTransformInViewport { path, transform }.into()).into()
|
||||
}
|
||||
(_, _) => Message::NoOp,
|
||||
};
|
||||
|
@ -195,7 +191,7 @@ impl Fsm for SelectToolFsmState {
|
|||
};
|
||||
buffer.into_iter().rev().for_each(|message| responses.push_front(message));
|
||||
|
||||
// TODO: Probably delete this now that the overlay system has moved to a separate Graphene document? (@0hypercube)
|
||||
// TODO: Probably delete this now that the overlays system has moved to a separate Graphene document? (@0hypercube)
|
||||
let ignore_layers = if let Some(bounding_box) = &data.bounding_box_overlay_layer {
|
||||
vec![bounding_box.clone()]
|
||||
} else {
|
||||
|
@ -240,7 +236,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let size = data.drag_current - start + half_pixel_offset;
|
||||
|
||||
responses.push_front(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: data.drag_box_overlay_layer.clone().unwrap(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
|
||||
|
@ -264,7 +260,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let quad = data.selection_quad();
|
||||
responses.push_front(DocumentMessage::AddSelectedLayers(document.graphene_document.intersects_quad_root(quad)).into());
|
||||
responses.push_front(
|
||||
DocumentMessage::Overlay(
|
||||
DocumentMessage::Overlays(
|
||||
Operation::DeleteLayer {
|
||||
path: data.drag_box_overlay_layer.take().unwrap(),
|
||||
}
|
||||
|
@ -275,7 +271,7 @@ impl Fsm for SelectToolFsmState {
|
|||
Ready
|
||||
}
|
||||
(_, Abort) => {
|
||||
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_front(DocumentMessage::Overlay(Operation::DeleteLayer { path }.into()).into()));
|
||||
let mut delete = |path: &mut Option<Vec<LayerId>>| path.take().map(|path| responses.push_front(DocumentMessage::Overlays(Operation::DeleteLayer { path }.into()).into()));
|
||||
delete(&mut data.drag_box_overlay_layer);
|
||||
delete(&mut data.bounding_box_overlay_layer);
|
||||
Ready
|
|
@ -1,13 +1,17 @@
|
|||
use super::shared::resize::Resize;
|
||||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::{Key, MouseMotion};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::misc::{HintData, HintGroup, HintInfo, KeysGroup};
|
||||
use crate::tool::{DocumentToolData, Fsm, ShapeType, ToolActionHandlerData, ToolOptions, ToolType};
|
||||
use crate::{document::DocumentMessageHandler, message_prelude::*};
|
||||
use glam::DAffine2;
|
||||
use graphene::{layers::style, Operation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::viewport_tools::tool::{DocumentToolData, Fsm, ToolActionHandlerData, ToolType};
|
||||
use crate::viewport_tools::tool_options::{ShapeType, ToolOptions};
|
||||
|
||||
use super::resize::*;
|
||||
use graphene::layers::style;
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Shape {
|
||||
|
@ -75,7 +79,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
document: &DocumentMessageHandler,
|
||||
tool_data: &DocumentToolData,
|
||||
data: &mut Self::ToolData,
|
||||
input: &InputPreprocessor,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let mut shape_data = &mut data.data;
|
1
editor/src/viewport_tools/tools/shared/mod.rs
Normal file
1
editor/src/viewport_tools/tools/shared/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod resize;
|
|
@ -1,11 +1,14 @@
|
|||
use crate::document::DocumentMessageHandler;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::tool::snapping::SnapHandler;
|
||||
use crate::tool::DocumentMessageHandler;
|
||||
use glam::{DAffine2, DVec2, Vec2Swizzles};
|
||||
use crate::viewport_tools::snapping::SnapHandler;
|
||||
|
||||
use graphene::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2, Vec2Swizzles};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Resize {
|
||||
pub drag_start: ViewportPosition,
|
||||
|
@ -20,7 +23,7 @@ impl Resize {
|
|||
self.drag_start = self.snap_handler.snap_position(document, mouse_position);
|
||||
}
|
||||
|
||||
pub fn calculate_transform(&self, document: &DocumentMessageHandler, center: Key, lock_ratio: Key, ipp: &InputPreprocessor) -> Option<Message> {
|
||||
pub fn calculate_transform(&self, document: &DocumentMessageHandler, center: Key, lock_ratio: Key, ipp: &InputPreprocessorMessageHandler) -> Option<Message> {
|
||||
if let Some(path) = &self.path {
|
||||
let mut start = self.drag_start;
|
||||
|
|
@ -1,28 +1,30 @@
|
|||
// This file is where functions are defined to be called directly from JS.
|
||||
// It serves as a thin wrapper over the editor backend API that relies
|
||||
// on the dispatcher messaging system and more complex Rust data types.
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::helpers::Error;
|
||||
use crate::type_translators::{translate_blend_mode, translate_key, translate_tool_type, translate_view_mode};
|
||||
use crate::{EDITOR_HAS_CRASHED, EDITOR_INSTANCES};
|
||||
|
||||
use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
|
||||
use editor::input::input_preprocessor::ModifierKeys;
|
||||
use editor::input::mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||
use editor::message_prelude::*;
|
||||
use editor::misc::EditorError;
|
||||
use editor::tool::{tool_options::ToolOptions, tools, ToolType};
|
||||
use editor::viewport_tools::tool::ToolType;
|
||||
use editor::viewport_tools::tool_options::ToolOptions;
|
||||
use editor::viewport_tools::tools;
|
||||
use editor::Color;
|
||||
use editor::Editor;
|
||||
use editor::LayerId;
|
||||
|
||||
use editor::Editor;
|
||||
use serde::Serialize;
|
||||
use serde_wasm_bindgen;
|
||||
use std::sync::atomic::Ordering;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell
|
||||
// we must make all methods take a non mutable reference to self. Not doing this creates
|
||||
// an issue when rust calls into JS which calls back to rust in the same call stack.
|
||||
// To avoid wasm-bindgen from checking mutable reference issues using WasmRefCell we must make all methods take a non mutable reference to self.
|
||||
// Not doing this creates an issue when rust calls into JS which calls back to rust in the same call stack.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Clone)]
|
||||
pub struct JsEditorHandle {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// The JavaScript `Error` type
|
||||
/// The JavaScript `Error` type
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
pub mod api;
|
||||
mod helpers;
|
||||
pub mod helpers;
|
||||
pub mod logging;
|
||||
pub mod type_translators;
|
||||
|
||||
use editor::message_prelude::FrontendMessage;
|
||||
use editor::message_prelude::*;
|
||||
|
||||
use logging::WasmLog;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
|
@ -12,13 +13,12 @@ use std::sync::atomic::AtomicBool;
|
|||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// Set up the persistent editor backend state
|
||||
static LOGGER: WasmLog = WasmLog;
|
||||
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
|
||||
pub static LOGGER: WasmLog = WasmLog;
|
||||
thread_local! {
|
||||
pub static EDITOR_INSTANCES: RefCell<HashMap<u64, (editor::Editor, api::JsEditorHandle)>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
||||
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
// Initialize the backend
|
||||
#[wasm_bindgen(start)]
|
||||
pub fn init() {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::helpers::match_string_to_enum;
|
||||
|
||||
use editor::input::keyboard::Key;
|
||||
use editor::tool::ToolType;
|
||||
use graphene::layers::{style::ViewMode, BlendMode};
|
||||
use editor::viewport_tools::tool::ToolType;
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::style::ViewMode;
|
||||
|
||||
pub fn translate_tool_type(name: &str) -> Option<ToolType> {
|
||||
use ToolType::*;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Structure that represent a color.
|
||||
/// Structure that represents a color.
|
||||
/// Internally alpha is stored as `f32` that ranges from `0.0` (transparent) to `1.0` (opaque).
|
||||
/// The other components (RGB) are stored as `f32` that range from `0.0` up to `f32::MAX`,
|
||||
/// the values encode the brightness of each channel proportional to the light intensity in cd/m² (nits) in HDR, and `0.0` (black) to `1.0` (white) in SDR color.
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use std::{
|
||||
cmp::max,
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
use crate::intersection::Quad;
|
||||
use crate::layers;
|
||||
use crate::layers::folder::Folder;
|
||||
use crate::layers::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use crate::layers::simple_shape::Shape;
|
||||
use crate::layers::style::ViewMode;
|
||||
use crate::{DocumentError, DocumentResponse, Operation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::max;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
use crate::{
|
||||
layers::{self, style::ViewMode, Folder, Layer, LayerData, LayerDataType, Shape},
|
||||
DocumentError, DocumentResponse, LayerId, Operation, Quad,
|
||||
};
|
||||
pub type LayerId = u64;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Document {
|
||||
|
|
12
graphene/src/error.rs
Normal file
12
graphene/src/error.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use super::LayerId;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DocumentError {
|
||||
LayerNotFound(Vec<LayerId>),
|
||||
InvalidPath,
|
||||
IndexOutOfBounds,
|
||||
NotAFolder,
|
||||
NonReorderableSelection,
|
||||
NotAShape,
|
||||
InvalidFile(String),
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use std::ops::Mul;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, Line, PathSeg, Point, Shape};
|
||||
use std::ops::Mul;
|
||||
|
||||
#[derive(Debug, Clone, Default, Copy)]
|
||||
pub struct Quad([DVec2; 4]);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use super::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use super::style::ViewMode;
|
||||
use crate::intersection::Quad;
|
||||
use crate::{DocumentError, LayerId};
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
use crate::{layers::style::ViewMode, DocumentError, LayerId, Quad};
|
||||
|
||||
use super::{Layer, LayerData, LayerDataType};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
||||
|
|
208
graphene/src/layers/layer_info.rs
Normal file
208
graphene/src/layers/layer_info.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
use super::blend_mode::BlendMode;
|
||||
use super::folder::Folder;
|
||||
use super::simple_shape::Shape;
|
||||
use super::style::ViewMode;
|
||||
use crate::intersection::Quad;
|
||||
use crate::DocumentError;
|
||||
use crate::LayerId;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum LayerDataType {
|
||||
Folder(Folder),
|
||||
Shape(Shape),
|
||||
}
|
||||
|
||||
impl LayerDataType {
|
||||
pub fn inner(&self) -> &dyn LayerData {
|
||||
match self {
|
||||
LayerDataType::Shape(s) => s,
|
||||
LayerDataType::Folder(f) => f,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut dyn LayerData {
|
||||
match self {
|
||||
LayerDataType::Shape(s) => s,
|
||||
LayerDataType::Folder(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayerData {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode);
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>);
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>;
|
||||
}
|
||||
|
||||
impl LayerData for LayerDataType {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode) {
|
||||
self.inner_mut().render(svg, transforms, view_mode)
|
||||
}
|
||||
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
self.inner().intersects_quad(quad, path, intersections)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.inner().bounding_box(transform)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "glam::DAffine2")]
|
||||
struct DAffine2Ref {
|
||||
pub matrix2: DMat2,
|
||||
pub translation: DVec2,
|
||||
}
|
||||
|
||||
fn return_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Layer {
|
||||
pub visible: bool,
|
||||
pub name: Option<String>,
|
||||
pub data: LayerDataType,
|
||||
#[serde(with = "DAffine2Ref")]
|
||||
pub transform: glam::DAffine2,
|
||||
#[serde(skip)]
|
||||
pub cache: String,
|
||||
#[serde(skip)]
|
||||
pub thumbnail_cache: String,
|
||||
#[serde(skip, default = "return_true")]
|
||||
pub cache_dirty: bool,
|
||||
pub blend_mode: BlendMode,
|
||||
pub opacity: f64,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(data: LayerDataType, transform: [f64; 6]) -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
name: None,
|
||||
data,
|
||||
transform: glam::DAffine2::from_cols_array(&transform),
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
cache_dirty: true,
|
||||
blend_mode: BlendMode::Normal,
|
||||
opacity: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> LayerIter<'_> {
|
||||
LayerIter { stack: vec![self] }
|
||||
}
|
||||
|
||||
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) -> &str {
|
||||
if !self.visible {
|
||||
return "";
|
||||
}
|
||||
|
||||
if self.cache_dirty {
|
||||
transforms.push(self.transform);
|
||||
self.thumbnail_cache.clear();
|
||||
self.data.render(&mut self.thumbnail_cache, transforms, view_mode);
|
||||
|
||||
self.cache.clear();
|
||||
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
|
||||
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
||||
let _ = self.cache.write_str(&(f.to_string() + if i != 5 { "," } else { "" }));
|
||||
});
|
||||
let _ = write!(
|
||||
self.cache,
|
||||
r#")" style="mix-blend-mode: {}; opacity: {}">{}</g>"#,
|
||||
self.blend_mode.to_svg_style_name(),
|
||||
self.opacity,
|
||||
self.thumbnail_cache.as_str()
|
||||
);
|
||||
transforms.pop();
|
||||
self.cache_dirty = false;
|
||||
}
|
||||
|
||||
self.cache.as_str()
|
||||
}
|
||||
|
||||
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
if !self.visible {
|
||||
return;
|
||||
}
|
||||
|
||||
let transformed_quad = self.transform.inverse() * quad;
|
||||
self.data.intersects_quad(transformed_quad, path, intersections)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.data.bounding_box(transform)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.current_bounding_box_with_transform(self.transform)
|
||||
}
|
||||
|
||||
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
||||
match &mut self.data {
|
||||
LayerDataType::Folder(f) => Ok(f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
|
||||
match &self.data {
|
||||
LayerDataType::Folder(f) => Ok(f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Layer {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
visible: self.visible,
|
||||
name: self.name.clone(),
|
||||
data: self.data.clone(),
|
||||
transform: self.transform,
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
cache_dirty: true,
|
||||
blend_mode: self.blend_mode,
|
||||
opacity: self.opacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Layer {
|
||||
type Item = &'a Layer;
|
||||
type IntoIter = LayerIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LayerIter<'a> {
|
||||
pub stack: Vec<&'a Layer>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LayerIter<'a> {
|
||||
type Item = &'a Layer;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(layer) => {
|
||||
if let LayerDataType::Folder(folder) = &layer.data {
|
||||
let layers = folder.layers();
|
||||
self.stack.extend(layers);
|
||||
};
|
||||
Some(layer)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,212 +1,5 @@
|
|||
pub mod style;
|
||||
use style::ViewMode;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::{DMat2, DVec2};
|
||||
|
||||
pub mod blend_mode;
|
||||
pub use blend_mode::BlendMode;
|
||||
|
||||
pub mod simple_shape;
|
||||
pub use simple_shape::Shape;
|
||||
|
||||
pub mod folder;
|
||||
use crate::LayerId;
|
||||
use crate::{DocumentError, Quad};
|
||||
pub use folder::Folder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum LayerDataType {
|
||||
Folder(Folder),
|
||||
Shape(Shape),
|
||||
}
|
||||
impl LayerDataType {
|
||||
pub fn inner(&self) -> &dyn LayerData {
|
||||
match self {
|
||||
LayerDataType::Shape(s) => s,
|
||||
LayerDataType::Folder(f) => f,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_mut(&mut self) -> &mut dyn LayerData {
|
||||
match self {
|
||||
LayerDataType::Shape(s) => s,
|
||||
LayerDataType::Folder(f) => f,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LayerData {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode);
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>);
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]>;
|
||||
}
|
||||
|
||||
impl LayerData for LayerDataType {
|
||||
fn render(&mut self, svg: &mut String, transforms: &mut Vec<glam::DAffine2>, view_mode: ViewMode) {
|
||||
self.inner_mut().render(svg, transforms, view_mode)
|
||||
}
|
||||
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
self.inner().intersects_quad(quad, path, intersections)
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.inner().bounding_box(transform)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "glam::DAffine2")]
|
||||
struct DAffine2Ref {
|
||||
pub matrix2: DMat2,
|
||||
pub translation: DVec2,
|
||||
}
|
||||
|
||||
fn return_true() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct Layer {
|
||||
pub visible: bool,
|
||||
pub name: Option<String>,
|
||||
pub data: LayerDataType,
|
||||
#[serde(with = "DAffine2Ref")]
|
||||
pub transform: glam::DAffine2,
|
||||
#[serde(skip)]
|
||||
pub cache: String,
|
||||
#[serde(skip)]
|
||||
pub thumbnail_cache: String,
|
||||
#[serde(skip, default = "return_true")]
|
||||
pub cache_dirty: bool,
|
||||
pub blend_mode: BlendMode,
|
||||
pub opacity: f64,
|
||||
}
|
||||
|
||||
impl Layer {
|
||||
pub fn new(data: LayerDataType, transform: [f64; 6]) -> Self {
|
||||
Self {
|
||||
visible: true,
|
||||
name: None,
|
||||
data,
|
||||
transform: glam::DAffine2::from_cols_array(&transform),
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
cache_dirty: true,
|
||||
blend_mode: BlendMode::Normal,
|
||||
opacity: 1.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> LayerIter<'_> {
|
||||
LayerIter { stack: vec![self] }
|
||||
}
|
||||
|
||||
pub fn render(&mut self, transforms: &mut Vec<DAffine2>, view_mode: ViewMode) -> &str {
|
||||
if !self.visible {
|
||||
return "";
|
||||
}
|
||||
if self.cache_dirty {
|
||||
transforms.push(self.transform);
|
||||
self.thumbnail_cache.clear();
|
||||
self.data.render(&mut self.thumbnail_cache, transforms, view_mode);
|
||||
|
||||
self.cache.clear();
|
||||
let _ = writeln!(self.cache, r#"<g transform="matrix("#);
|
||||
self.transform.to_cols_array().iter().enumerate().for_each(|(i, f)| {
|
||||
let _ = self.cache.write_str(&(f.to_string() + if i != 5 { "," } else { "" }));
|
||||
});
|
||||
let _ = write!(
|
||||
self.cache,
|
||||
r#")" style="mix-blend-mode: {}; opacity: {}">{}</g>"#,
|
||||
self.blend_mode.to_svg_style_name(),
|
||||
self.opacity,
|
||||
self.thumbnail_cache.as_str()
|
||||
);
|
||||
transforms.pop();
|
||||
self.cache_dirty = false;
|
||||
}
|
||||
self.cache.as_str()
|
||||
}
|
||||
|
||||
pub fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>) {
|
||||
if !self.visible {
|
||||
return;
|
||||
}
|
||||
let transformed_quad = self.transform.inverse() * quad;
|
||||
self.data.intersects_quad(transformed_quad, path, intersections)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.data.bounding_box(transform)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.current_bounding_box_with_transform(self.transform)
|
||||
}
|
||||
|
||||
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
||||
match &mut self.data {
|
||||
LayerDataType::Folder(f) => Ok(f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_folder(&self) -> Result<&Folder, DocumentError> {
|
||||
match &self.data {
|
||||
LayerDataType::Folder(f) => Ok(f),
|
||||
_ => Err(DocumentError::NotAFolder),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Layer {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
visible: self.visible,
|
||||
name: self.name.clone(),
|
||||
data: self.data.clone(),
|
||||
transform: self.transform,
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
cache_dirty: true,
|
||||
blend_mode: self.blend_mode,
|
||||
opacity: self.opacity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Layer {
|
||||
type Item = &'a Layer;
|
||||
type IntoIter = LayerIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct LayerIter<'a> {
|
||||
pub stack: Vec<&'a Layer>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for LayerIter<'a> {
|
||||
type Item = &'a Layer;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self.stack.pop() {
|
||||
Some(layer) => {
|
||||
if let LayerDataType::Folder(folder) = &layer.data {
|
||||
let layers = folder.layers();
|
||||
self.stack.extend(layers);
|
||||
};
|
||||
Some(layer)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod layer_info;
|
||||
pub mod simple_shape;
|
||||
pub mod style;
|
||||
|
|
|
@ -1,19 +1,10 @@
|
|||
use glam::DAffine2;
|
||||
use glam::DMat2;
|
||||
use glam::DVec2;
|
||||
use kurbo::Affine;
|
||||
use kurbo::BezPath;
|
||||
use kurbo::Shape as KurboShape;
|
||||
|
||||
use crate::intersection::intersect_quad_bez_path;
|
||||
use crate::layers::{
|
||||
style,
|
||||
style::{PathStyle, ViewMode},
|
||||
LayerData,
|
||||
};
|
||||
use super::layer_info::LayerData;
|
||||
use super::style::{self, PathStyle, ViewMode};
|
||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||
use crate::LayerId;
|
||||
use crate::Quad;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
|
||||
|
@ -107,6 +98,7 @@ impl Shape {
|
|||
relative_points.for_each(|p| path.line_to(p));
|
||||
|
||||
path.close_path();
|
||||
|
||||
Self {
|
||||
path,
|
||||
style,
|
||||
|
@ -146,6 +138,7 @@ impl Shape {
|
|||
.map(|v: DVec2| kurbo::Point { x: v.x, y: v.y })
|
||||
.enumerate()
|
||||
.for_each(|(i, p)| if i == 0 { path.move_to(p) } else { path.line_to(p) });
|
||||
|
||||
Self {
|
||||
path,
|
||||
style,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::color::Color;
|
||||
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WIDTH};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const OPACITY_PRECISION: usize = 3;
|
||||
|
@ -18,6 +19,7 @@ pub enum ViewMode {
|
|||
Outline,
|
||||
Pixels,
|
||||
}
|
||||
|
||||
impl Default for ViewMode {
|
||||
fn default() -> Self {
|
||||
ViewMode::Normal
|
||||
|
@ -29,16 +31,20 @@ impl Default for ViewMode {
|
|||
pub struct Fill {
|
||||
color: Option<Color>,
|
||||
}
|
||||
|
||||
impl Fill {
|
||||
pub fn new(color: Color) -> Self {
|
||||
Self { color: Some(color) }
|
||||
}
|
||||
|
||||
pub fn color(&self) -> Option<Color> {
|
||||
self.color
|
||||
}
|
||||
|
||||
pub const fn none() -> Self {
|
||||
Self { color: None }
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
match self.color {
|
||||
Some(c) => format!(r##" fill="#{}"{}"##, c.rgb_hex(), format_opacity("fill", c.a())),
|
||||
|
@ -58,12 +64,15 @@ impl Stroke {
|
|||
pub const fn new(color: Color, width: f32) -> Self {
|
||||
Self { color, width }
|
||||
}
|
||||
|
||||
pub fn color(&self) -> Color {
|
||||
self.color
|
||||
}
|
||||
|
||||
pub fn width(&self) -> f32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
format!(r##" stroke="#{}"{} stroke-width="{}""##, self.color.rgb_hex(), format_opacity("stroke", self.color.a()), self.width)
|
||||
}
|
||||
|
@ -75,25 +84,32 @@ pub struct PathStyle {
|
|||
stroke: Option<Stroke>,
|
||||
fill: Option<Fill>,
|
||||
}
|
||||
|
||||
impl PathStyle {
|
||||
pub fn new(stroke: Option<Stroke>, fill: Option<Fill>) -> Self {
|
||||
Self { stroke, fill }
|
||||
}
|
||||
|
||||
pub fn fill(&self) -> Option<Fill> {
|
||||
self.fill
|
||||
}
|
||||
|
||||
pub fn stroke(&self) -> Option<Stroke> {
|
||||
self.stroke
|
||||
}
|
||||
|
||||
pub fn set_fill(&mut self, fill: Fill) {
|
||||
self.fill = Some(fill);
|
||||
}
|
||||
|
||||
pub fn set_stroke(&mut self, stroke: Stroke) {
|
||||
self.stroke = Some(stroke);
|
||||
}
|
||||
|
||||
pub fn clear_fill(&mut self) {
|
||||
self.fill = None;
|
||||
}
|
||||
|
||||
pub fn clear_stroke(&mut self) {
|
||||
self.stroke = None;
|
||||
}
|
||||
|
@ -109,6 +125,7 @@ impl PathStyle {
|
|||
(_, Some(stroke)) => stroke.render(),
|
||||
(_, None) => String::new(),
|
||||
};
|
||||
|
||||
format!("{}{}", fill_attribute, stroke_attribute)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
pub mod color;
|
||||
pub mod consts;
|
||||
pub mod document;
|
||||
pub mod error;
|
||||
pub mod intersection;
|
||||
pub mod layers;
|
||||
pub mod operation;
|
||||
pub mod response;
|
||||
|
||||
pub use intersection::Quad;
|
||||
pub use document::LayerId;
|
||||
pub use error::DocumentError;
|
||||
pub use operation::Operation;
|
||||
pub use response::DocumentResponse;
|
||||
|
||||
pub type LayerId = u64;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DocumentError {
|
||||
LayerNotFound(Vec<LayerId>),
|
||||
InvalidPath,
|
||||
IndexOutOfBounds,
|
||||
NotAFolder,
|
||||
NonReorderableSelection,
|
||||
NotAShape,
|
||||
InvalidFile(String),
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
use std::{
|
||||
collections::hash_map::DefaultHasher,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
color::Color,
|
||||
layers::{style, BlendMode, Layer},
|
||||
LayerId,
|
||||
};
|
||||
use crate::color::Color;
|
||||
use crate::layers::blend_mode::BlendMode;
|
||||
use crate::layers::layer_info::Layer;
|
||||
use crate::layers::style;
|
||||
use crate::LayerId;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::LayerId;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use quote::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::discriminant::derive_discriminant_impl;
|
|||
use crate::helper_structs::AttrInnerSingleString;
|
||||
use crate::hint::derive_hint_impl;
|
||||
use crate::transitive_child::derive_transitive_child_impl;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
|
@ -276,6 +277,7 @@ pub fn edge(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
fn ts_assert_eq(l: TokenStream2, r: TokenStream2) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue