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:
Keavon Chambers 2022-01-14 14:58:08 -08:00
parent 011c2be26d
commit f48d4e1884
85 changed files with 2515 additions and 2189 deletions

View file

@ -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<_>>());
}
}

View file

@ -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.

View 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;
}

View file

@ -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);
}

View file

@ -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

View 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))
}
}

View file

@ -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]

View 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,
}

View 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()
}
}

View file

@ -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());

View file

@ -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 {

View file

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

View 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,
}

View file

@ -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,

View file

@ -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
)
}
}

View 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))
}
}

View 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)
}
}

View 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,
}

View file

@ -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,

View file

@ -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
}
}

View 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),
}

View 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
}
}

View 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;
}
}

View 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,
}

View file

@ -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,

View file

@ -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)]

View file

@ -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};

View 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,
}

View 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,
}

View file

@ -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);
}

View file

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

View file

@ -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;
}

View 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,
}

View 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!();
}

View file

@ -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();

View 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),
}

View 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());
}
}
}

View file

@ -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 {

View file

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

View file

@ -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,

View file

@ -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;
}

View file

@ -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.

View file

@ -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>);

View file

@ -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),*)
}
}
}

View file

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

View file

@ -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

View file

@ -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!(),
}
}
}

View 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;

View file

@ -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;
}

View 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(),
);
}

View 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,
}

View file

@ -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(),
);
}

View file

@ -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)]

View file

@ -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!();
}

View file

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

View file

@ -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 {

View file

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

View file

@ -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) => {

View file

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

View file

@ -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());

View file

@ -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
}

View file

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

View file

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

View file

@ -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

View file

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

View file

@ -0,0 +1 @@
pub mod resize;

View file

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

View file

@ -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 {

View file

@ -1,6 +1,6 @@
use wasm_bindgen::prelude::*;
// The JavaScript `Error` type
/// The JavaScript `Error` type
#[wasm_bindgen]
extern "C" {
#[derive(Clone, Debug)]

View file

@ -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() {

View file

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

View file

@ -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.

View file

@ -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
View 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),
}

View file

@ -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]);

View file

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

View 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,
}
}
}

View file

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

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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),
}

View file

@ -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)]

View file

@ -1,4 +1,5 @@
use crate::LayerId;
use serde::{Deserialize, Serialize};
use std::fmt;

View file

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

View file

@ -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) {