mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Massively reorganize and clean up the whole Rust codebase (#478)
* Massively reorganize and clean up the whole Rust codebase * Additional changes during code review
This commit is contained in:
20
editor/src/document/artboard_message.rs
Normal file
20
editor/src/document/artboard_message.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Artboard)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ArtboardMessage {
|
||||
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
RenderArtboards,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for ArtboardMessage {
|
||||
fn from(operation: DocumentOperation) -> Self {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,16 @@
|
||||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::{DocumentMessage, LayerMetadata};
|
||||
use crate::input::InputPreprocessor;
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
use graphene::color::Color;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::{self, Fill, ViewMode};
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Artboard)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ArtboardMessage {
|
||||
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
RenderArtboards,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for ArtboardMessage {
|
||||
fn from(operation: DocumentOperation) -> Self {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct ArtboardMessageHandler {
|
||||
pub artboards_graphene_document: GrapheneDocument,
|
||||
@@ -37,9 +23,9 @@ impl ArtboardMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
|
||||
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessorMessageHandler)> for ArtboardMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: ArtboardMessage, _data: (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use ArtboardMessage::*;
|
||||
#[remain::sorted]
|
||||
|
||||
21
editor/src/document/clipboards.rs
Normal file
21
editor/src/document/clipboards.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use super::layer_panel::LayerMetadata;
|
||||
|
||||
use graphene::layers::layer_info::Layer;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Clipboard {
|
||||
System,
|
||||
User,
|
||||
_ClipboardCount, // Keep this as the last entry since it is used for counting the number of enum variants
|
||||
}
|
||||
|
||||
pub const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CopyBufferEntry {
|
||||
pub layer: Layer,
|
||||
pub layer_metadata: LayerMetadata,
|
||||
}
|
||||
88
editor/src/document/document_message.rs
Normal file
88
editor/src/document/document_message.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use super::utility_types::{AlignAggregate, AlignAxis, FlipAxis};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::LayerId;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
AbortTransaction,
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||
#[child]
|
||||
Artboard(ArtboardMessage),
|
||||
CommitTransaction,
|
||||
CreateEmptyFolder(Vec<LayerId>),
|
||||
DebugPrintDocument,
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
DeleteSelectedLayers,
|
||||
DeselectAllLayers,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DuplicateSelectedLayers,
|
||||
ExportDocument,
|
||||
FlipSelectedLayers(FlipAxis),
|
||||
FolderChanged(Vec<LayerId>),
|
||||
GroupSelectedLayers,
|
||||
LayerChanged(Vec<LayerId>),
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
MoveSelectedLayersTo {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
NudgeSelectedLayers(f64, f64),
|
||||
#[child]
|
||||
Overlays(OverlaysMessage),
|
||||
Redo,
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
RenderDocument,
|
||||
ReorderSelectedLayers(i32), // relative_position,
|
||||
RollbackTransaction,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectionChanged,
|
||||
SelectLayer(Vec<LayerId>, bool, bool),
|
||||
SetBlendModeForSelectedLayers(BlendMode),
|
||||
SetLayerExpansion(Vec<LayerId>, bool),
|
||||
SetOpacityForSelectedLayers(f64),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SetSnapping(bool),
|
||||
SetViewMode(ViewMode),
|
||||
StartTransaction,
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
Undo,
|
||||
UngroupLayers(Vec<LayerId>),
|
||||
UngroupSelectedLayers,
|
||||
UpdateLayerMetadata {
|
||||
layer_path: Vec<LayerId>,
|
||||
layer_metadata: LayerMetadata,
|
||||
},
|
||||
ZoomCanvasToFitAll,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
fn from(operation: DocumentOperation) -> DocumentMessage {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||
}
|
||||
}
|
||||
@@ -1,73 +1,27 @@
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::artboard_message_handler::ArtboardMessage;
|
||||
use super::artboard_message_handler::ArtboardMessageHandler;
|
||||
pub use super::layer_panel::*;
|
||||
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
||||
use super::overlay_message_handler::OverlayMessageHandler;
|
||||
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
|
||||
use super::clipboards::Clipboard;
|
||||
use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||
use super::vectorize_layer_metadata;
|
||||
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
|
||||
use crate::consts::{
|
||||
ASYMPTOTIC_EFFECT, DEFAULT_DOCUMENT_NAME, FILE_EXPORT_SUFFIX, FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR,
|
||||
};
|
||||
use crate::document::Clipboard;
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::EditorError;
|
||||
|
||||
use graphene::layers::{style::ViewMode, BlendMode, LayerDataType};
|
||||
use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId};
|
||||
use graphene::{DocumentResponse, Operation as DocumentOperation};
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::folder::Folder;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::layers::Folder;
|
||||
use kurbo::PathSeg;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum FlipAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAggregate {
|
||||
Min,
|
||||
Max,
|
||||
Center,
|
||||
Average,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum VectorManipulatorSegment {
|
||||
Line(DVec2, DVec2),
|
||||
Quad(DVec2, DVec2, DVec2),
|
||||
Cubic(DVec2, DVec2, DVec2, DVec2),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct VectorManipulatorShape {
|
||||
/// The path to the layer
|
||||
pub layer_path: Vec<LayerId>,
|
||||
/// The outline of the shape
|
||||
pub path: kurbo::BezPath,
|
||||
/// The control points / manipulator handles
|
||||
pub segments: Vec<VectorManipulatorSegment>,
|
||||
/// The compound Bezier curve is closed
|
||||
pub closed: bool,
|
||||
/// The transformation matrix to apply
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
use std::collections::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct DocumentMessageHandler {
|
||||
@@ -83,7 +37,7 @@ pub struct DocumentMessageHandler {
|
||||
layer_range_selection_reference: Vec<LayerId>,
|
||||
movement_handler: MovementMessageHandler,
|
||||
#[serde(skip)]
|
||||
overlay_message_handler: OverlayMessageHandler,
|
||||
overlays_message_handler: OverlaysMessageHandler,
|
||||
artboard_message_handler: ArtboardMessageHandler,
|
||||
#[serde(skip)]
|
||||
transform_layer_handler: TransformLayerMessageHandler,
|
||||
@@ -98,12 +52,12 @@ impl Default for DocumentMessageHandler {
|
||||
graphene_document: GrapheneDocument::default(),
|
||||
document_undo_history: Vec::new(),
|
||||
document_redo_history: Vec::new(),
|
||||
name: String::from("Untitled Document"),
|
||||
saved_document_identifier: 0,
|
||||
name: String::from("Untitled Document"),
|
||||
layer_metadata: vec![(vec![], LayerMetadata::new(true))].into_iter().collect(),
|
||||
layer_range_selection_reference: Vec::new(),
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
overlay_message_handler: OverlayMessageHandler::default(),
|
||||
overlays_message_handler: OverlaysMessageHandler::default(),
|
||||
artboard_message_handler: ArtboardMessageHandler::default(),
|
||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||
snapping_enabled: true,
|
||||
@@ -113,84 +67,6 @@ impl Default for DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
AbortTransaction,
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||
#[child]
|
||||
Artboard(ArtboardMessage),
|
||||
CommitTransaction,
|
||||
CreateEmptyFolder(Vec<LayerId>),
|
||||
DebugPrintDocument,
|
||||
DeleteLayer(Vec<LayerId>),
|
||||
DeleteSelectedLayers,
|
||||
DeselectAllLayers,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DuplicateSelectedLayers,
|
||||
ExportDocument,
|
||||
FlipSelectedLayers(FlipAxis),
|
||||
FolderChanged(Vec<LayerId>),
|
||||
GroupSelectedLayers,
|
||||
LayerChanged(Vec<LayerId>),
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
MoveSelectedLayersTo {
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
NudgeSelectedLayers(f64, f64),
|
||||
#[child]
|
||||
Overlay(OverlayMessage),
|
||||
Redo,
|
||||
RenameLayer(Vec<LayerId>, String),
|
||||
RenderDocument,
|
||||
ReorderSelectedLayers(i32), // relative_position,
|
||||
RollbackTransaction,
|
||||
SaveDocument,
|
||||
SelectAllLayers,
|
||||
SelectionChanged,
|
||||
SelectLayer(Vec<LayerId>, bool, bool),
|
||||
SetBlendModeForSelectedLayers(BlendMode),
|
||||
SetLayerExpansion(Vec<LayerId>, bool),
|
||||
SetOpacityForSelectedLayers(f64),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SetSnapping(bool),
|
||||
SetViewMode(ViewMode),
|
||||
StartTransaction,
|
||||
ToggleLayerExpansion(Vec<LayerId>),
|
||||
ToggleLayerVisibility(Vec<LayerId>),
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
Undo,
|
||||
UngroupLayers(Vec<LayerId>),
|
||||
UngroupSelectedLayers,
|
||||
UpdateLayerMetadata {
|
||||
layer_path: Vec<LayerId>,
|
||||
layer_metadata: LayerMetadata,
|
||||
},
|
||||
ZoomCanvasToFitAll,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for DocumentMessage {
|
||||
fn from(operation: DocumentOperation) -> DocumentMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
pub fn serialize_document(&self) -> String {
|
||||
let val = serde_json::to_string(self);
|
||||
@@ -212,7 +88,7 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_name(name: String, ipp: &InputPreprocessor) -> Self {
|
||||
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler) -> Self {
|
||||
let mut document = Self { name, ..Self::default() };
|
||||
let starting_root_transform = document.movement_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
|
||||
document.graphene_document.root.transform = starting_root_transform;
|
||||
@@ -256,13 +132,13 @@ impl DocumentMessageHandler {
|
||||
self.graphene_document.combined_viewport_bounding_box(paths)
|
||||
}
|
||||
|
||||
// TODO: Consider moving this to some kind of overlay manager in the future
|
||||
// TODO: Consider moving this to some kind of overlays manager in the future
|
||||
pub fn selected_visible_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
|
||||
let shapes = self.selected_layers().filter_map(|path_to_shape| {
|
||||
let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?;
|
||||
let layer = self.graphene_document.layer(path_to_shape);
|
||||
|
||||
// Filter out the non-visible layers from the filter_map
|
||||
// Filter out the non-visible layers from the `filter_map`
|
||||
match &layer {
|
||||
Ok(layer) if layer.visible => {}
|
||||
_ => return None,
|
||||
@@ -343,30 +219,38 @@ impl DocumentMessageHandler {
|
||||
structure.push(space | 1 << 63);
|
||||
}
|
||||
|
||||
/// Serializes the layer structure into a compressed 1d structure
|
||||
/// Serializes the layer structure into a condensed 1D structure.
|
||||
///
|
||||
/// # Format
|
||||
/// It is a string of numbers broken into three sections:
|
||||
/// (4),(2,1,-2,-0),(16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186) <- Example encoded data
|
||||
/// L = 4 = structure.len() <- First value in the encoding: L, the length of the structure section
|
||||
/// structure = 2,1,-2,-0 <- Subsequent L values: structure section
|
||||
/// data = 16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186 <- Remaining values: data section (layer IDs)
|
||||
///
|
||||
/// | Data | Description | Length |
|
||||
/// |--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------|
|
||||
/// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | |
|
||||
/// | `L` = `4` = `structure.len()` | `L`, the length of the **Structure** section | First value |
|
||||
/// | **Structure** section = `2, 1, -2, -0` | The **Structure** section | Next `L` values |
|
||||
/// | **Data** section = `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values |
|
||||
///
|
||||
/// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom.
|
||||
/// The structure section lists signed numbers. The sign indicates a folder indentation change (+ is down a level, - is up a level).
|
||||
/// the numbers in the structure block encode the indentation,
|
||||
/// 2 mean read two element from the data section, then place a [
|
||||
/// -x means read x elements from the data section and then insert a ]
|
||||
/// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level).
|
||||
/// The numbers in the structure block encode the indentation. For example:
|
||||
/// - `2` means read two element from the data section, then place a `[`.
|
||||
/// - `-x` means read `x` elements from the data section and then insert a `]`.
|
||||
///
|
||||
/// ```text
|
||||
/// 2 V 1 V -2 A -0 A
|
||||
/// 16533113728871998040,3427872634365736244, 18115028555707261608, 15878401910454357952,449479075714955186
|
||||
/// 16533113728871998040,3427872634365736244,[ 18115028555707261608,[15878401910454357952,449479075714955186] ]
|
||||
/// ```
|
||||
///
|
||||
/// resulting layer panel:
|
||||
/// Resulting layer panel:
|
||||
/// ```text
|
||||
/// 16533113728871998040
|
||||
/// 3427872634365736244
|
||||
/// [3427872634365736244,18115028555707261608]
|
||||
/// [3427872634365736244,18115028555707261608,15878401910454357952]
|
||||
/// [3427872634365736244,18115028555707261608,449479075714955186]
|
||||
/// ```
|
||||
pub fn serialize_root(&self) -> Vec<u64> {
|
||||
let (mut structure, mut data) = (vec![0], Vec::new());
|
||||
self.serialize_structure(self.graphene_document.root.as_folder().unwrap(), &mut structure, &mut data, &mut vec![]);
|
||||
@@ -552,9 +436,9 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
|
||||
impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for DocumentMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: DocumentMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
@@ -798,13 +682,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
Overlay(message) => {
|
||||
self.overlay_message_handler.process_action(
|
||||
Overlays(message) => {
|
||||
self.overlays_message_handler.process_action(
|
||||
message,
|
||||
(Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp),
|
||||
responses,
|
||||
);
|
||||
// responses.push_back(OverlayMessage::RenderOverlays.into());
|
||||
// responses.push_back(OverlaysMessage::RenderOverlays.into());
|
||||
}
|
||||
Redo => {
|
||||
responses.push_back(SelectMessage::Abort.into());
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use graphene::layers::{style::ViewMode, BlendMode, Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::blend_mode::BlendMode;
|
||||
use graphene::layers::layer_info::{Layer, LayerData, LayerDataType};
|
||||
use graphene::layers::style::ViewMode;
|
||||
use graphene::LayerId;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{ser::SerializeStruct, Deserialize, Serialize};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Copy)]
|
||||
pub struct LayerMetadata {
|
||||
|
||||
@@ -1,29 +1,48 @@
|
||||
pub mod clipboards;
|
||||
pub mod layer_panel;
|
||||
pub mod transformation;
|
||||
pub mod utility_types;
|
||||
pub mod vectorize_layer_metadata;
|
||||
|
||||
mod artboard_message;
|
||||
mod artboard_message_handler;
|
||||
mod document_message;
|
||||
mod document_message_handler;
|
||||
mod layer_panel;
|
||||
mod movement_handler;
|
||||
mod overlay_message_handler;
|
||||
mod movement_message;
|
||||
mod movement_message_handler;
|
||||
mod overlays_message;
|
||||
mod overlays_message_handler;
|
||||
mod portfolio_message;
|
||||
mod portfolio_message_handler;
|
||||
mod transform_layer_handler;
|
||||
mod vectorize_layer_metadata;
|
||||
mod transform_layer_message;
|
||||
mod transform_layer_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||
pub use artboard_message::{ArtboardMessage, ArtboardMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use artboard_message_handler::ArtboardMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use layer_panel::{LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::DocumentMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
||||
pub use movement_message::{MovementMessage, MovementMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use movement_message_handler::MovementMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant};
|
||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use overlays_message_handler::OverlaysMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use portfolio_message_handler::{Clipboard, PortfolioMessage, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
||||
pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use portfolio_message_handler::PortfolioMessageHandler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use artboard_message_handler::{ArtboardMessage, ArtboardMessageDiscriminant};
|
||||
|
||||
pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
pub use transform_layer_message_handler::TransformLayerMessageHandler;
|
||||
|
||||
40
editor/src/document/movement_message.rs
Normal file
40
editor/src/document/movement_message.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
DecreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
FitViewportToBounds {
|
||||
bounds: [DVec2; 2],
|
||||
padding_scale_factor: Option<f32>,
|
||||
prevent_zoom_past_100: bool,
|
||||
},
|
||||
IncreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
MouseMove {
|
||||
snap_angle: Key,
|
||||
wait_for_snap_angle_release: bool,
|
||||
snap_zoom: Key,
|
||||
zoom_from_viewport: Option<DVec2>,
|
||||
},
|
||||
RotateCanvasBegin,
|
||||
SetCanvasRotation(f64),
|
||||
SetCanvasZoom(f64),
|
||||
TransformCanvasEnd,
|
||||
TranslateCanvas(DVec2),
|
||||
TranslateCanvasBegin,
|
||||
TranslateCanvasByViewportFraction(DVec2),
|
||||
WheelCanvasTranslate {
|
||||
use_y_as_x: bool,
|
||||
},
|
||||
WheelCanvasZoom,
|
||||
ZoomCanvasBegin,
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
use crate::consts::VIEWPORT_ROTATE_SNAP_INTERVAL;
|
||||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::DocumentMessage;
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::consts::{VIEWPORT_ROTATE_SNAP_INTERVAL, VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE};
|
||||
use crate::input::mouse::{ViewportBounds, ViewportPosition};
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use crate::{
|
||||
consts::{VIEWPORT_SCROLL_RATE, VIEWPORT_ZOOM_LEVELS, VIEWPORT_ZOOM_MOUSE_RATE, VIEWPORT_ZOOM_SCALE_MAX, VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_WHEEL_RATE},
|
||||
input::{mouse::ViewportBounds, mouse::ViewportPosition, InputPreprocessor},
|
||||
};
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
@@ -14,41 +10,6 @@ use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
DecreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
FitViewportToBounds {
|
||||
bounds: [DVec2; 2],
|
||||
padding_scale_factor: Option<f32>,
|
||||
prevent_zoom_past_100: bool,
|
||||
},
|
||||
IncreaseCanvasZoom {
|
||||
center_on_mouse: bool,
|
||||
},
|
||||
MouseMove {
|
||||
snap_angle: Key,
|
||||
wait_for_snap_angle_release: bool,
|
||||
snap_zoom: Key,
|
||||
zoom_from_viewport: Option<DVec2>,
|
||||
},
|
||||
RotateCanvasBegin,
|
||||
SetCanvasRotation(f64),
|
||||
SetCanvasZoom(f64),
|
||||
TransformCanvasEnd,
|
||||
TranslateCanvas(DVec2),
|
||||
TranslateCanvasBegin,
|
||||
TranslateCanvasByViewportFraction(DVec2),
|
||||
WheelCanvasTranslate {
|
||||
use_y_as_x: bool,
|
||||
},
|
||||
WheelCanvasZoom,
|
||||
ZoomCanvasBegin,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct MovementMessageHandler {
|
||||
pub pan: DVec2,
|
||||
@@ -138,6 +99,7 @@ impl MovementMessageHandler {
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
@@ -148,9 +110,9 @@ impl MovementMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for MovementMessageHandler {
|
||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandler)> for MovementMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: MovementMessage, data: (&Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
let (document, ipp) = data;
|
||||
use MovementMessage::*;
|
||||
#[remain::sorted]
|
||||
@@ -325,6 +287,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(MovementMessageDiscriminant;
|
||||
MouseMove,
|
||||
@@ -1,59 +0,0 @@
|
||||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::{DocumentMessage, LayerMetadata};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Overlay)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OverlayMessage {
|
||||
ClearAllOverlays,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for OverlayMessage {
|
||||
fn from(operation: DocumentOperation) -> OverlayMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlayMessageHandler {
|
||||
pub overlays_graphene_document: GrapheneDocument,
|
||||
}
|
||||
|
||||
impl MessageHandler<OverlayMessage, (&mut LayerMetadata, &Document, &InputPreprocessor)> for OverlayMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: OverlayMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use OverlayMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ClearAllOverlays => todo!(),
|
||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||
Ok(_) => (),
|
||||
Err(e) => log::error!("OverlayError: {:?}", e),
|
||||
},
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentOverlays {
|
||||
svg: self.overlays_graphene_document.render_root(ViewMode::Normal),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlayMessageDiscriminant;
|
||||
ClearAllOverlays
|
||||
)
|
||||
}
|
||||
}
|
||||
19
editor/src/document/overlays_message.rs
Normal file
19
editor/src/document/overlays_message.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, Overlays)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OverlaysMessage {
|
||||
ClearAllOverlays,
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for OverlaysMessage {
|
||||
fn from(operation: DocumentOperation) -> OverlaysMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
40
editor/src/document/overlays_message_handler.rs
Normal file
40
editor/src/document/overlays_message_handler.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlaysMessageHandler {
|
||||
pub overlays_graphene_document: GrapheneDocument,
|
||||
}
|
||||
|
||||
impl MessageHandler<OverlaysMessage, (&mut LayerMetadata, &Document, &InputPreprocessorMessageHandler)> for OverlaysMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: OverlaysMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||
// let (layer_metadata, document, ipp) = data;
|
||||
use OverlaysMessage::*;
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ClearAllOverlays => todo!(),
|
||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||
Ok(_) => (),
|
||||
Err(e) => log::error!("OverlaysError: {:?}", e),
|
||||
},
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateDocumentOverlays {
|
||||
svg: self.overlays_graphene_document.render_root(ViewMode::Normal),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlaysMessageDiscriminant; ClearAllOverlays)
|
||||
}
|
||||
}
|
||||
43
editor/src/document/portfolio_message.rs
Normal file
43
editor/src/document/portfolio_message.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use super::clipboards::Clipboard;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::LayerId;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Portfolio)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PortfolioMessage {
|
||||
AutoSaveActiveDocument,
|
||||
AutoSaveDocument(u64),
|
||||
CloseActiveDocumentWithConfirmation,
|
||||
CloseAllDocuments,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDocument(u64),
|
||||
CloseDocumentWithConfirmation(u64),
|
||||
Copy(Clipboard),
|
||||
Cut(Clipboard),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
NewDocument,
|
||||
NextDocument,
|
||||
OpenDocument,
|
||||
OpenDocumentFile(String, String),
|
||||
OpenDocumentFileWithId {
|
||||
document: String,
|
||||
document_name: String,
|
||||
document_id: u64,
|
||||
document_is_saved: bool,
|
||||
},
|
||||
Paste(Clipboard),
|
||||
PasteIntoFolder {
|
||||
clipboard: Clipboard,
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
PrevDocument,
|
||||
RequestAboutGraphiteDialog,
|
||||
SelectDocument(u64),
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
@@ -1,63 +1,15 @@
|
||||
use super::{DocumentMessageHandler, LayerMetadata};
|
||||
use super::clipboards::{CopyBufferEntry, CLIPBOARD_COUNT};
|
||||
use super::DocumentMessageHandler;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
use crate::frontend::frontend_message_handler::FrontendDocumentDetails;
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::frontend::utility_types::FrontendDocumentDetails;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::layers::Layer;
|
||||
use graphene::{LayerId, Operation as DocumentOperation};
|
||||
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Clipboard {
|
||||
System,
|
||||
User,
|
||||
_ClipboardCount,
|
||||
}
|
||||
|
||||
const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, Portfolio)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum PortfolioMessage {
|
||||
AutoSaveActiveDocument,
|
||||
AutoSaveDocument(u64),
|
||||
CloseActiveDocumentWithConfirmation,
|
||||
CloseAllDocuments,
|
||||
CloseAllDocumentsWithConfirmation,
|
||||
CloseDocument(u64),
|
||||
CloseDocumentWithConfirmation(u64),
|
||||
Copy(Clipboard),
|
||||
Cut(Clipboard),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
NewDocument,
|
||||
NextDocument,
|
||||
OpenDocument,
|
||||
OpenDocumentFile(String, String),
|
||||
OpenDocumentFileWithId {
|
||||
document: String,
|
||||
document_name: String,
|
||||
document_id: u64,
|
||||
document_is_saved: bool,
|
||||
},
|
||||
Paste(Clipboard),
|
||||
PasteIntoFolder {
|
||||
clipboard: Clipboard,
|
||||
path: Vec<LayerId>,
|
||||
insert_index: isize,
|
||||
},
|
||||
PrevDocument,
|
||||
RequestAboutGraphiteDialog,
|
||||
SelectDocument(u64),
|
||||
UpdateOpenDocumentsList,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
documents: HashMap<u64, DocumentMessageHandler>,
|
||||
@@ -66,12 +18,6 @@ pub struct PortfolioMessageHandler {
|
||||
copy_buffer: [Vec<CopyBufferEntry>; CLIPBOARD_COUNT as usize],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CopyBufferEntry {
|
||||
layer: Layer,
|
||||
layer_metadata: LayerMetadata,
|
||||
}
|
||||
|
||||
impl PortfolioMessageHandler {
|
||||
pub fn active_document(&self) -> &DocumentMessageHandler {
|
||||
self.documents.get(&self.active_document_id).unwrap()
|
||||
@@ -149,7 +95,7 @@ impl PortfolioMessageHandler {
|
||||
responses.push_back(PortfolioMessage::SelectDocument(document_id).into());
|
||||
}
|
||||
|
||||
// Returns an iterator over the open documents in order
|
||||
/// Returns an iterator over the open documents in order.
|
||||
pub fn ordered_document_iterator(&self) -> impl Iterator<Item = &DocumentMessageHandler> {
|
||||
self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap"))
|
||||
}
|
||||
@@ -176,9 +122,9 @@ impl Default for PortfolioMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHandler {
|
||||
impl MessageHandler<PortfolioMessage, &InputPreprocessorMessageHandler> for PortfolioMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||
fn process_action(&mut self, message: PortfolioMessage, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
use DocumentMessage::*;
|
||||
use PortfolioMessage::*;
|
||||
#[remain::sorted]
|
||||
@@ -429,6 +375,7 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(PortfolioMessageDiscriminant;
|
||||
NewDocument,
|
||||
|
||||
@@ -1,550 +0,0 @@
|
||||
pub use super::layer_panel::*;
|
||||
|
||||
use super::LayerMetadata;
|
||||
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL, SLOWING_DIVISOR};
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::message_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
|
||||
|
||||
struct Selected<'a> {
|
||||
pub selected: Vec<Vec<LayerId>>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
}
|
||||
impl<'a> Selected<'a> {
|
||||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
) -> Self {
|
||||
let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect();
|
||||
for path in &selected {
|
||||
if !original_transforms.contains_key::<Vec<LayerId>>(path) {
|
||||
original_transforms.insert(path.clone(), document.layer(path).unwrap().transform);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
original_transforms,
|
||||
pivot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
let bounds = self
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.current_bounding_box_with_transform(multiplied_transform)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
for layer_path in &self.selected {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap();
|
||||
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in &self.selected {
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.to_vec(),
|
||||
transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Axis {
|
||||
Both,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for Axis {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn set_or_toggle(&mut self, target: Axis) {
|
||||
// If constrained to an axis and target is requesting the same axis, toggle back to Both
|
||||
if *self == target {
|
||||
*self = Axis::Both;
|
||||
}
|
||||
// If current axis is different from the target axis, switch to the target
|
||||
else {
|
||||
*self = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Translation {
|
||||
pub dragged_distance: DVec2,
|
||||
pub typed_distance: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn to_dvec(self) -> DVec2 {
|
||||
if let Some(value) = self.typed_distance {
|
||||
if self.constraint == Axis::Y {
|
||||
return DVec2::new(0., value);
|
||||
} else {
|
||||
return DVec2::new(value, 0.);
|
||||
}
|
||||
}
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
Axis::X => DVec2::new(self.dragged_distance.x, 0.),
|
||||
Axis::Y => DVec2::new(0., self.dragged_distance.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: DVec2) -> Self {
|
||||
Self {
|
||||
dragged_distance: self.dragged_distance + delta,
|
||||
typed_distance: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
struct Scale {
|
||||
pub dragged_factor: f64,
|
||||
pub typed_factor: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dragged_factor: 1.,
|
||||
typed_factor: None,
|
||||
constraint: Axis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||
let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor };
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
Axis::X => DVec2::new(factor, 1.),
|
||||
Axis::Y => DVec2::new(1., factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Rotation {
|
||||
pub dragged_angle: f64,
|
||||
pub typed_angle: Option<f64>,
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
pub fn to_f64(self, snap: bool) -> f64 {
|
||||
if let Some(value) = self.typed_angle {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_angle: self.dragged_angle + delta,
|
||||
typed_angle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Operation {
|
||||
None,
|
||||
Grabbing(Translation),
|
||||
Rotating(Rotation),
|
||||
Scaling(Scale),
|
||||
}
|
||||
|
||||
impl Default for Operation {
|
||||
fn default() -> Self {
|
||||
Operation::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn apply_operation(&self, selected: &mut Selected, snapping: bool) {
|
||||
if self != &Operation::None {
|
||||
let transformation = match self {
|
||||
Operation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()),
|
||||
Operation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
||||
Operation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)),
|
||||
Operation::None => unreachable!(),
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.constraint.set_or_toggle(axis),
|
||||
Operation::Rotating(_) => (),
|
||||
Operation::Scaling(scale) => scale.constraint.set_or_toggle(axis),
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn handle_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
Operation::Rotating(rotation) => rotation.typed_angle = typed,
|
||||
Operation::Scaling(scale) => scale.typed_factor = typed,
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct Typing {
|
||||
digits: Vec<u8>,
|
||||
contains_decimal: bool,
|
||||
negative: bool,
|
||||
}
|
||||
|
||||
const DECIMAL_POINT: u8 = 10;
|
||||
|
||||
impl Typing {
|
||||
pub fn type_number(&mut self, number: u8) -> Option<f64> {
|
||||
self.digits.push(number);
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_backspace(&mut self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.digits.pop() {
|
||||
Some(DECIMAL_POINT) => self.contains_decimal = false,
|
||||
Some(_) => (),
|
||||
None => self.negative = false,
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_decimal_point(&mut self) -> Option<f64> {
|
||||
if !self.contains_decimal {
|
||||
self.contains_decimal = true;
|
||||
self.digits.push(DECIMAL_POINT);
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_negate(&mut self) -> Option<f64> {
|
||||
self.negative = !self.negative;
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = 0_f64;
|
||||
let mut running_decimal_place = 0_i32;
|
||||
|
||||
for digit in &self.digits {
|
||||
if *digit == DECIMAL_POINT {
|
||||
if running_decimal_place == 0 {
|
||||
running_decimal_place = 1;
|
||||
}
|
||||
} else if running_decimal_place == 0 {
|
||||
result *= 10.;
|
||||
result += *digit as f64;
|
||||
} else {
|
||||
result += *digit as f64 * 0.1_f64.powi(running_decimal_place);
|
||||
running_decimal_place += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.negative {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.digits.clear();
|
||||
self.contains_decimal = false;
|
||||
self.negative = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TransformLayerMessage {
|
||||
ApplyOperation,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
CancelOperation,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
MouseMove { slow_key: Key, snap_key: Key },
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
TypeNumber(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
operation: Operation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
typing: Typing,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor)> for TransformLayerMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(&mut self, message: TransformLayerMessage, data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
let (layer_metadata, document, ipp) = data;
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document);
|
||||
|
||||
let mut begin_operation = |operation: Operation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
|
||||
if !(operation == Operation::None) {
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
};
|
||||
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ApplyOperation => {
|
||||
self.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginGrab => {
|
||||
if let Operation::Grabbing(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let Operation::Rotating(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let Operation::Scaling(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Scaling(Default::default());
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
CancelOperation => {
|
||||
selected.revert_operation();
|
||||
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_key as usize);
|
||||
if new_snap != self.snap {
|
||||
self.snap = new_snap;
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
|
||||
if self.typing.digits.is_empty() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
match self.operation {
|
||||
Operation::None => unreachable!(),
|
||||
Operation::Grabbing(translation) => {
|
||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||
self.operation = Operation::Grabbing(translation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot();
|
||||
let angle = {
|
||||
let start_vec = self.mouse_position - selected_pivot;
|
||||
let end_vec = ipp.mouse.position - selected_pivot;
|
||||
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
self.operation = Operation::Rotating(rotation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::Scaling(scale) => {
|
||||
let change = {
|
||||
let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
|
||||
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length();
|
||||
let start_transform_dist = (self.start_mouse - *selected.pivot).length();
|
||||
|
||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||
};
|
||||
|
||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||
self.operation = Operation::Scaling(scale.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TypeBackspace => self.operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
TypeNegate => self.operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||
TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
);
|
||||
|
||||
if self.operation != Operation::None {
|
||||
let active = actions!(TransformLayerMessageDiscriminant;
|
||||
MouseMove,
|
||||
CancelOperation,
|
||||
ApplyOperation,
|
||||
TypeNumber,
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
);
|
||||
common.extend(active);
|
||||
}
|
||||
|
||||
common
|
||||
}
|
||||
}
|
||||
22
editor/src/document/transform_layer_message.rs
Normal file
22
editor/src/document/transform_layer_message.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TransformLayerMessage {
|
||||
ApplyTransformOperation,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
CancelTransformOperation,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
MouseMove { slow_key: Key, snap_key: Key },
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
TypeNumber(u8),
|
||||
}
|
||||
189
editor/src/document/transform_layer_message_handler.rs
Normal file
189
editor/src/document/transform_layer_message_handler.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use super::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
|
||||
use crate::consts::SLOWING_DIVISOR;
|
||||
use crate::input::mouse::ViewportPosition;
|
||||
use crate::input::InputPreprocessorMessageHandler;
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
|
||||
use glam::DVec2;
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
transform_operation: TransformOperation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
typing: Typing,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler)> for TransformLayerMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_action(
|
||||
&mut self,
|
||||
message: TransformLayerMessage,
|
||||
data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessorMessageHandler),
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
let (layer_metadata, document, ipp) = data;
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document);
|
||||
|
||||
let mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
|
||||
if !(operation == TransformOperation::None) {
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
};
|
||||
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
ApplyTransformOperation => {
|
||||
self.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginGrab => {
|
||||
if let TransformOperation::Grabbing(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let TransformOperation::Rotating(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let TransformOperation::Scaling(_) = self.transform_operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.transform_operation = TransformOperation::Scaling(Default::default());
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
CancelTransformOperation => {
|
||||
selected.revert_operation();
|
||||
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_key as usize);
|
||||
if new_snap != self.snap {
|
||||
self.snap = new_snap;
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
|
||||
if self.typing.digits.is_empty() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
match self.transform_operation {
|
||||
TransformOperation::None => unreachable!(),
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||
self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot();
|
||||
let angle = {
|
||||
let start_vec = self.mouse_position - selected_pivot;
|
||||
let end_vec = ipp.mouse.position - selected_pivot;
|
||||
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Scaling(scale) => {
|
||||
let change = {
|
||||
let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
|
||||
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length();
|
||||
let start_transform_dist = (self.start_mouse - *selected.pivot).length();
|
||||
|
||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||
};
|
||||
|
||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||
self.transform_operation = TransformOperation::Scaling(scale.increment_amount(change));
|
||||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
TypeNegate => self.transform_operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||
TypeNumber(number) => self.transform_operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
);
|
||||
|
||||
if self.transform_operation != TransformOperation::None {
|
||||
let active = actions!(TransformLayerMessageDiscriminant;
|
||||
MouseMove,
|
||||
CancelTransformOperation,
|
||||
ApplyTransformOperation,
|
||||
TypeNumber,
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
);
|
||||
common.extend(active);
|
||||
}
|
||||
|
||||
common
|
||||
}
|
||||
}
|
||||
356
editor/src/document/transformation.rs
Normal file
356
editor/src/document/transformation.rs
Normal file
@@ -0,0 +1,356 @@
|
||||
use super::layer_panel::LayerMetadata;
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||
use crate::message_prelude::*;
|
||||
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
pub type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum Axis {
|
||||
Both,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for Axis {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn set_or_toggle(&mut self, target: Axis) {
|
||||
// If constrained to an axis and target is requesting the same axis, toggle back to Both
|
||||
if *self == target {
|
||||
*self = Axis::Both;
|
||||
}
|
||||
// If current axis is different from the target axis, switch to the target
|
||||
else {
|
||||
*self = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Translation {
|
||||
pub dragged_distance: DVec2,
|
||||
pub typed_distance: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn to_dvec(self) -> DVec2 {
|
||||
if let Some(value) = self.typed_distance {
|
||||
if self.constraint == Axis::Y {
|
||||
return DVec2::new(0., value);
|
||||
} else {
|
||||
return DVec2::new(value, 0.);
|
||||
}
|
||||
}
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
Axis::X => DVec2::new(self.dragged_distance.x, 0.),
|
||||
Axis::Y => DVec2::new(0., self.dragged_distance.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: DVec2) -> Self {
|
||||
Self {
|
||||
dragged_distance: self.dragged_distance + delta,
|
||||
typed_distance: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Rotation {
|
||||
pub dragged_angle: f64,
|
||||
pub typed_angle: Option<f64>,
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
pub fn to_f64(self, snap: bool) -> f64 {
|
||||
if let Some(value) = self.typed_angle {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_angle: self.dragged_angle + delta,
|
||||
typed_angle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub struct Scale {
|
||||
pub dragged_factor: f64,
|
||||
pub typed_factor: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dragged_factor: 1.,
|
||||
typed_factor: None,
|
||||
constraint: Axis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||
let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor };
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
Axis::X => DVec2::new(factor, 1.),
|
||||
Axis::Y => DVec2::new(1., factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
pub enum TransformOperation {
|
||||
None,
|
||||
Grabbing(Translation),
|
||||
Rotating(Rotation),
|
||||
Scaling(Scale),
|
||||
}
|
||||
|
||||
impl Default for TransformOperation {
|
||||
fn default() -> Self {
|
||||
TransformOperation::None
|
||||
}
|
||||
}
|
||||
|
||||
impl TransformOperation {
|
||||
pub fn apply_transform_operation(&self, selected: &mut Selected, snapping: bool) {
|
||||
if self != &TransformOperation::None {
|
||||
let transformation = match self {
|
||||
TransformOperation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()),
|
||||
TransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
||||
TransformOperation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)),
|
||||
TransformOperation::None => unreachable!(),
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => translation.constraint.set_or_toggle(axis),
|
||||
TransformOperation::Rotating(_) => (),
|
||||
TransformOperation::Scaling(scale) => scale.constraint.set_or_toggle(axis),
|
||||
};
|
||||
|
||||
self.apply_transform_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn handle_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
TransformOperation::Rotating(rotation) => rotation.typed_angle = typed,
|
||||
TransformOperation::Scaling(scale) => scale.typed_factor = typed,
|
||||
};
|
||||
|
||||
self.apply_transform_operation(selected, snapping);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selected<'a> {
|
||||
pub selected: Vec<Vec<LayerId>>,
|
||||
pub responses: &'a mut VecDeque<Message>,
|
||||
pub document: &'a mut Document,
|
||||
pub original_transforms: &'a mut OriginalTransforms,
|
||||
pub pivot: &'a mut DVec2,
|
||||
}
|
||||
|
||||
impl<'a> Selected<'a> {
|
||||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
) -> Self {
|
||||
let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect();
|
||||
for path in &selected {
|
||||
if !original_transforms.contains_key::<Vec<LayerId>>(path) {
|
||||
original_transforms.insert(path.clone(), document.layer(path).unwrap().transform);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
original_transforms,
|
||||
pivot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
let bounds = self
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.current_bounding_box_with_transform(multiplied_transform)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
for layer_path in &self.selected {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap();
|
||||
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in &self.selected {
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.to_vec(),
|
||||
transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Typing {
|
||||
pub digits: Vec<u8>,
|
||||
pub contains_decimal: bool,
|
||||
pub negative: bool,
|
||||
}
|
||||
|
||||
const DECIMAL_POINT: u8 = 10;
|
||||
|
||||
impl Typing {
|
||||
pub fn type_number(&mut self, number: u8) -> Option<f64> {
|
||||
self.digits.push(number);
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_backspace(&mut self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.digits.pop() {
|
||||
Some(DECIMAL_POINT) => self.contains_decimal = false,
|
||||
Some(_) => (),
|
||||
None => self.negative = false,
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_decimal_point(&mut self) -> Option<f64> {
|
||||
if !self.contains_decimal {
|
||||
self.contains_decimal = true;
|
||||
self.digits.push(DECIMAL_POINT);
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_negate(&mut self) -> Option<f64> {
|
||||
self.negative = !self.negative;
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = 0_f64;
|
||||
let mut running_decimal_place = 0_i32;
|
||||
|
||||
for digit in &self.digits {
|
||||
if *digit == DECIMAL_POINT {
|
||||
if running_decimal_place == 0 {
|
||||
running_decimal_place = 1;
|
||||
}
|
||||
} else if running_decimal_place == 0 {
|
||||
result *= 10.;
|
||||
result += *digit as f64;
|
||||
} else {
|
||||
result += *digit as f64 * 0.1_f64.powi(running_decimal_place);
|
||||
running_decimal_place += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.negative {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.digits.clear();
|
||||
self.contains_decimal = false;
|
||||
self.negative = false;
|
||||
}
|
||||
}
|
||||
51
editor/src/document/utility_types.rs
Normal file
51
editor/src/document/utility_types.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
pub use super::layer_panel::{layer_panel_entry, LayerMetadata, LayerPanelEntry, RawBuffer};
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::LayerId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerMetadata>);
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum FlipAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAxis {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
pub enum AlignAggregate {
|
||||
Min,
|
||||
Max,
|
||||
Center,
|
||||
Average,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum VectorManipulatorSegment {
|
||||
Line(DVec2, DVec2),
|
||||
Quad(DVec2, DVec2, DVec2),
|
||||
Cubic(DVec2, DVec2, DVec2, DVec2),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct VectorManipulatorShape {
|
||||
/// The path to the layer
|
||||
pub layer_path: Vec<LayerId>,
|
||||
/// The outline of the shape
|
||||
pub path: kurbo::BezPath,
|
||||
/// The control points / manipulator handles
|
||||
pub segments: Vec<VectorManipulatorSegment>,
|
||||
/// The compound Bezier curve is closed
|
||||
pub closed: bool,
|
||||
/// The transformation matrix to apply
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
/// Necessary because serde can't serialize hashmaps when the keys don't implement display.
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::iter::FromIterator;
|
||||
|
||||
/// Necessary because serde can't serialize hashmaps when the keys don't implement display.
|
||||
pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
|
||||
Reference in New Issue
Block a user