mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 15:08:12 +08:00
Sort messages and message handlers
This commit is contained in:
Generated
+12
@@ -96,6 +96,7 @@ dependencies = [
|
|||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
|
"remain",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"spin",
|
"spin",
|
||||||
@@ -268,6 +269,17 @@ version = "0.6.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remain"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ba1e78fa68412cb93ef642fd4d20b9a941be49ee9333875ebaf13112673ea7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ spin = "0.9.2"
|
|||||||
kurbo = { git = "https://github.com/GraphiteEditor/kurbo.git", features = [
|
kurbo = { git = "https://github.com/GraphiteEditor/kurbo.git", features = [
|
||||||
"serde",
|
"serde",
|
||||||
] }
|
] }
|
||||||
|
remain = "0.2.2"
|
||||||
|
|
||||||
[dependencies.graphene]
|
[dependencies.graphene]
|
||||||
path = "../graphene"
|
path = "../graphene"
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ use graphene::Operation as DocumentOperation;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, DocumentMessage, Artboard)]
|
#[impl_message(Message, DocumentMessage, Artboard)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum ArtboardMessage {
|
pub enum ArtboardMessage {
|
||||||
DispatchOperation(Box<DocumentOperation>),
|
|
||||||
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
AddArtboard { top: f64, left: f64, height: f64, width: f64 },
|
||||||
|
DispatchOperation(Box<DocumentOperation>),
|
||||||
RenderArtboards,
|
RenderArtboards,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,14 +38,12 @@ impl ArtboardMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> for ArtboardMessageHandler {
|
impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &InputPreprocessor)> 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, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||||
// let (layer_metadata, document, ipp) = data;
|
// let (layer_metadata, document, ipp) = data;
|
||||||
use ArtboardMessage::*;
|
use ArtboardMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(&operation) {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(e) => log::error!("Artboard Error: {:?}", e),
|
|
||||||
},
|
|
||||||
AddArtboard { top, left, height, width } => {
|
AddArtboard { top, left, height, width } => {
|
||||||
let artboard_id = generate_uuid();
|
let artboard_id = generate_uuid();
|
||||||
self.artboard_ids.push(artboard_id);
|
self.artboard_ids.push(artboard_id);
|
||||||
@@ -64,6 +63,10 @@ impl MessageHandler<ArtboardMessage, (&mut LayerMetadata, &GrapheneDocument, &In
|
|||||||
|
|
||||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||||
}
|
}
|
||||||
|
DispatchOperation(operation) => match self.artboards_graphene_document.handle_operation(&operation) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(e) => log::error!("Artboard Error: {:?}", e),
|
||||||
|
},
|
||||||
RenderArtboards => {
|
RenderArtboards => {
|
||||||
// Render an infinite canvas if there are no artboards
|
// Render an infinite canvas if there are no artboards
|
||||||
if self.artboard_ids.is_empty() {
|
if self.artboard_ids.is_empty() {
|
||||||
|
|||||||
@@ -113,68 +113,69 @@ impl Default for DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, PortfolioMessage, Document)]
|
#[impl_message(Message, PortfolioMessage, Document)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum DocumentMessage {
|
pub enum DocumentMessage {
|
||||||
#[child]
|
AbortTransaction,
|
||||||
Movement(MovementMessage),
|
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||||
#[child]
|
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||||
TransformLayers(TransformLayerMessage),
|
|
||||||
DispatchOperation(Box<DocumentOperation>),
|
|
||||||
#[child]
|
|
||||||
Overlay(OverlayMessage),
|
|
||||||
#[child]
|
#[child]
|
||||||
Artboard(ArtboardMessage),
|
Artboard(ArtboardMessage),
|
||||||
UpdateLayerMetadata {
|
CommitTransaction,
|
||||||
layer_path: Vec<LayerId>,
|
CreateEmptyFolder(Vec<LayerId>),
|
||||||
layer_metadata: LayerMetadata,
|
|
||||||
},
|
|
||||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
|
||||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
|
||||||
SelectAllLayers,
|
|
||||||
DebugPrintDocument,
|
DebugPrintDocument,
|
||||||
SelectLayer(Vec<LayerId>, bool, bool),
|
|
||||||
SelectionChanged,
|
|
||||||
DeselectAllLayers,
|
|
||||||
DeleteLayer(Vec<LayerId>),
|
DeleteLayer(Vec<LayerId>),
|
||||||
DeleteSelectedLayers,
|
DeleteSelectedLayers,
|
||||||
DuplicateSelectedLayers,
|
DeselectAllLayers,
|
||||||
CreateEmptyFolder(Vec<LayerId>),
|
|
||||||
SetBlendModeForSelectedLayers(BlendMode),
|
|
||||||
SetOpacityForSelectedLayers(f64),
|
|
||||||
RenameLayer(Vec<LayerId>, String),
|
|
||||||
ToggleLayerVisibility(Vec<LayerId>),
|
|
||||||
FlipSelectedLayers(FlipAxis),
|
|
||||||
ToggleLayerExpansion(Vec<LayerId>),
|
|
||||||
SetLayerExpansion(Vec<LayerId>, bool),
|
|
||||||
FolderChanged(Vec<LayerId>),
|
|
||||||
LayerChanged(Vec<LayerId>),
|
|
||||||
DocumentStructureChanged,
|
|
||||||
StartTransaction,
|
|
||||||
RollbackTransaction,
|
|
||||||
GroupSelectedLayers,
|
|
||||||
UngroupSelectedLayers,
|
|
||||||
UngroupLayers(Vec<LayerId>),
|
|
||||||
AbortTransaction,
|
|
||||||
CommitTransaction,
|
|
||||||
ExportDocument,
|
|
||||||
SaveDocument,
|
|
||||||
RenderDocument,
|
|
||||||
DirtyRenderDocument,
|
DirtyRenderDocument,
|
||||||
DirtyRenderDocumentInOutlineView,
|
DirtyRenderDocumentInOutlineView,
|
||||||
SetViewMode(ViewMode),
|
DispatchOperation(Box<DocumentOperation>),
|
||||||
Undo,
|
|
||||||
Redo,
|
|
||||||
DocumentHistoryBackward,
|
DocumentHistoryBackward,
|
||||||
DocumentHistoryForward,
|
DocumentHistoryForward,
|
||||||
NudgeSelectedLayers(f64, f64),
|
DocumentStructureChanged,
|
||||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
DuplicateSelectedLayers,
|
||||||
|
ExportDocument,
|
||||||
|
FlipSelectedLayers(FlipAxis),
|
||||||
|
FolderChanged(Vec<LayerId>),
|
||||||
|
GroupSelectedLayers,
|
||||||
|
LayerChanged(Vec<LayerId>),
|
||||||
|
#[child]
|
||||||
|
Movement(MovementMessage),
|
||||||
MoveSelectedLayersTo {
|
MoveSelectedLayersTo {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
},
|
},
|
||||||
|
NudgeSelectedLayers(f64, f64),
|
||||||
|
#[child]
|
||||||
|
Overlay(OverlayMessage),
|
||||||
|
Redo,
|
||||||
|
RenameLayer(Vec<LayerId>, String),
|
||||||
|
RenderDocument,
|
||||||
ReorderSelectedLayers(i32), // relative_position,
|
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),
|
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,
|
ZoomCanvasToFitAll,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,243 +553,15 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHandler {
|
impl MessageHandler<DocumentMessage, &InputPreprocessor> 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: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||||
use DocumentMessage::*;
|
use DocumentMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
Movement(message) => self.movement_handler.process_action(message, (&self.graphene_document, ipp), responses),
|
|
||||||
TransformLayers(message) => self
|
|
||||||
.transform_layer_handler
|
|
||||||
.process_action(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp), responses),
|
|
||||||
DeleteLayer(path) => responses.push_front(DocumentOperation::DeleteLayer { path }.into()),
|
|
||||||
StartTransaction => self.backup(responses),
|
|
||||||
RollbackTransaction => {
|
|
||||||
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
|
||||||
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
|
||||||
}
|
|
||||||
AbortTransaction => {
|
AbortTransaction => {
|
||||||
self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
||||||
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
||||||
}
|
}
|
||||||
CommitTransaction => (),
|
|
||||||
Overlay(message) => {
|
|
||||||
self.overlay_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());
|
|
||||||
}
|
|
||||||
Artboard(message) => {
|
|
||||||
self.artboard_message_handler.process_action(
|
|
||||||
message,
|
|
||||||
(Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp),
|
|
||||||
responses,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ExportDocument => {
|
|
||||||
// TODO(MFISH33): Add Dialog to select artboards
|
|
||||||
let bbox = self.document_bounds().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
|
||||||
let size = bbox[1] - bbox[0];
|
|
||||||
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
|
||||||
true => self.name.clone().replace(FILE_SAVE_SUFFIX, FILE_EXPORT_SUFFIX),
|
|
||||||
false => self.name.clone() + FILE_EXPORT_SUFFIX,
|
|
||||||
};
|
|
||||||
responses.push_back(
|
|
||||||
FrontendMessage::TriggerFileDownload {
|
|
||||||
document: format!(
|
|
||||||
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}">{}{}</svg>"#,
|
|
||||||
bbox[0].x,
|
|
||||||
bbox[0].y,
|
|
||||||
size.x,
|
|
||||||
size.y,
|
|
||||||
"\n",
|
|
||||||
self.graphene_document.render_root(self.view_mode)
|
|
||||||
),
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SaveDocument => {
|
|
||||||
self.set_save_state(true);
|
|
||||||
responses.push_back(PortfolioMessage::AutoSaveActiveDocument.into());
|
|
||||||
// Update the save status of the just saved document
|
|
||||||
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
|
||||||
|
|
||||||
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
|
||||||
true => self.name.clone(),
|
|
||||||
false => self.name.clone() + FILE_SAVE_SUFFIX,
|
|
||||||
};
|
|
||||||
responses.push_back(
|
|
||||||
FrontendMessage::TriggerFileDownload {
|
|
||||||
document: self.serialize_document(),
|
|
||||||
name,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
CreateEmptyFolder(mut path) => {
|
|
||||||
let id = generate_uuid();
|
|
||||||
path.push(id);
|
|
||||||
responses.push_back(DocumentOperation::CreateFolder { path: path.clone() }.into());
|
|
||||||
responses.push_back(DocumentMessage::SetLayerExpansion(path, true).into());
|
|
||||||
}
|
|
||||||
GroupSelectedLayers => {
|
|
||||||
let mut new_folder_path: Vec<u64> = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
|
||||||
|
|
||||||
// Required for grouping parent folders with their own children
|
|
||||||
if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) {
|
|
||||||
new_folder_path.remove(new_folder_path.len() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_folder_path.push(generate_uuid());
|
|
||||||
|
|
||||||
responses.push_back(PortfolioMessage::Copy(Clipboard::System).into());
|
|
||||||
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
|
||||||
responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into());
|
|
||||||
responses.push_back(DocumentMessage::ToggleLayerExpansion(new_folder_path.clone()).into());
|
|
||||||
responses.push_back(
|
|
||||||
PortfolioMessage::PasteIntoFolder {
|
|
||||||
clipboard: Clipboard::System,
|
|
||||||
path: new_folder_path.clone(),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
responses.push_back(DocumentMessage::SetSelectedLayers(vec![new_folder_path]).into());
|
|
||||||
}
|
|
||||||
UngroupLayers(folder_path) => {
|
|
||||||
// Select all the children of the folder
|
|
||||||
let to_select = self.graphene_document.folder_children_paths(&folder_path);
|
|
||||||
let message_buffer = [
|
|
||||||
// Copy them
|
|
||||||
DocumentMessage::SetSelectedLayers(to_select).into(),
|
|
||||||
PortfolioMessage::Copy(Clipboard::System).into(),
|
|
||||||
// Paste them into the folder above
|
|
||||||
PortfolioMessage::PasteIntoFolder {
|
|
||||||
clipboard: Clipboard::System,
|
|
||||||
path: folder_path[..folder_path.len() - 1].to_vec(),
|
|
||||||
insert_index: -1,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
// Delete parent folder
|
|
||||||
DocumentMessage::DeleteLayer(folder_path).into(),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Push these messages in reverse due to push_front
|
|
||||||
for message in message_buffer.into_iter().rev() {
|
|
||||||
responses.push_front(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UngroupSelectedLayers => {
|
|
||||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
|
||||||
let folder_paths = self.graphene_document.sorted_folders_by_depth(self.selected_layers());
|
|
||||||
for folder_path in folder_paths {
|
|
||||||
responses.push_back(DocumentMessage::UngroupLayers(folder_path.to_vec()).into());
|
|
||||||
}
|
|
||||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
|
||||||
}
|
|
||||||
SetBlendModeForSelectedLayers(blend_mode) => {
|
|
||||||
self.backup(responses);
|
|
||||||
for path in self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
|
||||||
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetOpacityForSelectedLayers(opacity) => {
|
|
||||||
self.backup(responses);
|
|
||||||
let opacity = opacity.clamp(0., 1.);
|
|
||||||
|
|
||||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
|
||||||
responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToggleLayerVisibility(path) => {
|
|
||||||
responses.push_back(DocumentOperation::ToggleLayerVisibility { path }.into());
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
ToggleLayerExpansion(path) => {
|
|
||||||
self.layer_metadata_mut(&path).expanded ^= true;
|
|
||||||
responses.push_back(DocumentStructureChanged.into());
|
|
||||||
responses.push_back(LayerChanged(path).into())
|
|
||||||
}
|
|
||||||
SetLayerExpansion(path, is_expanded) => {
|
|
||||||
self.layer_metadata_mut(&path).expanded = is_expanded;
|
|
||||||
responses.push_back(DocumentStructureChanged.into());
|
|
||||||
responses.push_back(LayerChanged(path).into())
|
|
||||||
}
|
|
||||||
SelectionChanged => {
|
|
||||||
// TODO: Hoist this duplicated code into wider system
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
DeleteSelectedLayers => {
|
|
||||||
self.backup(responses);
|
|
||||||
|
|
||||||
for path in self.selected_layers_without_children() {
|
|
||||||
responses.push_front(DocumentOperation::DeleteLayer { path: path.to_vec() }.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.push_front(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
SetViewMode(mode) => {
|
|
||||||
self.view_mode = mode;
|
|
||||||
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
|
||||||
}
|
|
||||||
DuplicateSelectedLayers => {
|
|
||||||
self.backup(responses);
|
|
||||||
for path in self.selected_layers_sorted() {
|
|
||||||
responses.push_back(DocumentOperation::DuplicateLayer { path: path.to_vec() }.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SelectLayer(selected, ctrl, shift) => {
|
|
||||||
let mut paths = vec![];
|
|
||||||
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
|
||||||
|
|
||||||
// If we have shift pressed and a layer already selected then fill the range
|
|
||||||
if shift && last_selection_exists {
|
|
||||||
// Fill the selection range
|
|
||||||
self.layer_metadata
|
|
||||||
.iter()
|
|
||||||
.filter(|(target, _)| self.graphene_document.layer_is_between(target, &selected, &self.layer_range_selection_reference))
|
|
||||||
.for_each(|(layer_path, _)| {
|
|
||||||
paths.push(layer_path.clone());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if ctrl {
|
|
||||||
// Toggle selection when holding ctrl
|
|
||||||
let layer = self.layer_metadata_mut(&selected);
|
|
||||||
layer.selected = !layer.selected;
|
|
||||||
responses.push_back(LayerChanged(selected.clone()).into());
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
} else {
|
|
||||||
paths.push(selected.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set our last selection reference
|
|
||||||
self.layer_range_selection_reference = selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't create messages for empty operations
|
|
||||||
if !paths.is_empty() {
|
|
||||||
// Add or set our selected layers
|
|
||||||
if ctrl {
|
|
||||||
responses.push_front(AddSelectedLayers(paths).into());
|
|
||||||
} else {
|
|
||||||
responses.push_front(SetSelectedLayers(paths).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateLayerMetadata { layer_path: path, layer_metadata } => {
|
|
||||||
self.layer_metadata.insert(path, layer_metadata);
|
|
||||||
}
|
|
||||||
SetSelectedLayers(paths) => {
|
|
||||||
let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected);
|
|
||||||
selected.for_each(|(path, layer_metadata)| {
|
|
||||||
layer_metadata.selected = false;
|
|
||||||
responses.push_back(LayerChanged(path.clone()).into())
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.push_front(AddSelectedLayers(paths).into());
|
|
||||||
}
|
|
||||||
AddSelectedLayers(paths) => {
|
AddSelectedLayers(paths) => {
|
||||||
for path in paths {
|
for path in paths {
|
||||||
responses.extend(self.select_layer(&path));
|
responses.extend(self.select_layer(&path));
|
||||||
@@ -797,44 +570,83 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
responses.push_back(FolderChanged(Vec::new()).into());
|
responses.push_back(FolderChanged(Vec::new()).into());
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
}
|
}
|
||||||
|
AlignSelectedLayers(axis, aggregate) => {
|
||||||
|
self.backup(responses);
|
||||||
|
let (paths, boxes): (Vec<_>, Vec<_>) = self
|
||||||
|
.selected_layers()
|
||||||
|
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?.map(|b| (path, b)))
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
let axis = match axis {
|
||||||
|
AlignAxis::X => DVec2::X,
|
||||||
|
AlignAxis::Y => DVec2::Y,
|
||||||
|
};
|
||||||
|
let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5);
|
||||||
|
if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) {
|
||||||
|
let aggregated = match aggregate {
|
||||||
|
AlignAggregate::Min => combined_box[0],
|
||||||
|
AlignAggregate::Max => combined_box[1],
|
||||||
|
AlignAggregate::Center => lerp(&combined_box),
|
||||||
|
AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(),
|
||||||
|
};
|
||||||
|
for (path, bbox) in paths.into_iter().zip(boxes) {
|
||||||
|
let center = match aggregate {
|
||||||
|
AlignAggregate::Min => bbox[0],
|
||||||
|
AlignAggregate::Max => bbox[1],
|
||||||
|
_ => lerp(&bbox),
|
||||||
|
};
|
||||||
|
let translation = (aggregated - center) * axis;
|
||||||
|
responses.push_back(
|
||||||
|
DocumentOperation::TransformLayerInViewport {
|
||||||
|
path: path.to_vec(),
|
||||||
|
transform: DAffine2::from_translation(translation).to_cols_array(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Artboard(message) => {
|
||||||
|
self.artboard_message_handler.process_action(
|
||||||
|
message,
|
||||||
|
(Self::layer_metadata_mut_no_borrow_self(&mut self.layer_metadata, &[]), &self.graphene_document, ipp),
|
||||||
|
responses,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
CommitTransaction => (),
|
||||||
|
CreateEmptyFolder(mut path) => {
|
||||||
|
let id = generate_uuid();
|
||||||
|
path.push(id);
|
||||||
|
responses.push_back(DocumentOperation::CreateFolder { path: path.clone() }.into());
|
||||||
|
responses.push_back(DocumentMessage::SetLayerExpansion(path, true).into());
|
||||||
|
}
|
||||||
DebugPrintDocument => {
|
DebugPrintDocument => {
|
||||||
log::debug!("{:#?}\n{:#?}", self.graphene_document, self.layer_metadata);
|
log::debug!("{:#?}\n{:#?}", self.graphene_document, self.layer_metadata);
|
||||||
}
|
}
|
||||||
SelectAllLayers => {
|
DeleteLayer(path) => responses.push_front(DocumentOperation::DeleteLayer { path }.into()),
|
||||||
let all_layer_paths = self.all_layers();
|
DeleteSelectedLayers => {
|
||||||
responses.push_front(SetSelectedLayers(all_layer_paths.map(|path| path.to_vec()).collect()).into());
|
self.backup(responses);
|
||||||
|
|
||||||
|
for path in self.selected_layers_without_children() {
|
||||||
|
responses.push_front(DocumentOperation::DeleteLayer { path: path.to_vec() }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.push_front(ToolMessage::DocumentIsDirty.into());
|
||||||
}
|
}
|
||||||
DeselectAllLayers => {
|
DeselectAllLayers => {
|
||||||
responses.push_front(SetSelectedLayers(vec![]).into());
|
responses.push_front(SetSelectedLayers(vec![]).into());
|
||||||
self.layer_range_selection_reference.clear();
|
self.layer_range_selection_reference.clear();
|
||||||
}
|
}
|
||||||
DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
DirtyRenderDocument => {
|
||||||
DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
// Mark all non-overlay caches as dirty
|
||||||
Undo => {
|
GrapheneDocument::visit_all_shapes(&mut self.graphene_document.root, &mut |_| {});
|
||||||
responses.push_back(SelectMessage::Abort.into());
|
|
||||||
responses.push_back(DocumentHistoryBackward.into());
|
responses.push_back(DocumentMessage::RenderDocument.into());
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
responses.push_back(RenderDocument.into());
|
|
||||||
responses.push_back(FolderChanged(vec![]).into());
|
|
||||||
}
|
}
|
||||||
Redo => {
|
DirtyRenderDocumentInOutlineView => {
|
||||||
responses.push_back(SelectMessage::Abort.into());
|
if self.view_mode == ViewMode::Outline {
|
||||||
responses.push_back(DocumentHistoryForward.into());
|
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
responses.push_back(RenderDocument.into());
|
|
||||||
responses.push_back(FolderChanged(vec![]).into());
|
|
||||||
}
|
|
||||||
FolderChanged(path) => {
|
|
||||||
let _ = self.graphene_document.render_root(self.view_mode);
|
|
||||||
responses.extend([LayerChanged(path).into(), DocumentStructureChanged.into()]);
|
|
||||||
}
|
|
||||||
DocumentStructureChanged => {
|
|
||||||
let data_buffer: RawBuffer = self.serialize_root().into();
|
|
||||||
responses.push_back(FrontendMessage::DisplayDocumentLayerTreeStructure { data_buffer }.into())
|
|
||||||
}
|
|
||||||
LayerChanged(path) => {
|
|
||||||
if let Ok(layer_entry) = self.layer_panel_entry(path) {
|
|
||||||
responses.push_back(FrontendMessage::UpdateDocumentLayer { data: layer_entry }.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DispatchOperation(op) => match self.graphene_document.handle_operation(&op) {
|
DispatchOperation(op) => match self.graphene_document.handle_operation(&op) {
|
||||||
@@ -864,6 +676,144 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
Err(e) => log::error!("DocumentError: {:?}", e),
|
Err(e) => log::error!("DocumentError: {:?}", e),
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
},
|
},
|
||||||
|
DocumentHistoryBackward => self.undo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
|
DocumentHistoryForward => self.redo(responses).unwrap_or_else(|e| log::warn!("{}", e)),
|
||||||
|
DocumentStructureChanged => {
|
||||||
|
let data_buffer: RawBuffer = self.serialize_root().into();
|
||||||
|
responses.push_back(FrontendMessage::DisplayDocumentLayerTreeStructure { data_buffer }.into())
|
||||||
|
}
|
||||||
|
DuplicateSelectedLayers => {
|
||||||
|
self.backup(responses);
|
||||||
|
for path in self.selected_layers_sorted() {
|
||||||
|
responses.push_back(DocumentOperation::DuplicateLayer { path: path.to_vec() }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ExportDocument => {
|
||||||
|
// TODO(MFISH33): Add Dialog to select artboards
|
||||||
|
let bbox = self.document_bounds().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
||||||
|
let size = bbox[1] - bbox[0];
|
||||||
|
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
||||||
|
true => self.name.clone().replace(FILE_SAVE_SUFFIX, FILE_EXPORT_SUFFIX),
|
||||||
|
false => self.name.clone() + FILE_EXPORT_SUFFIX,
|
||||||
|
};
|
||||||
|
responses.push_back(
|
||||||
|
FrontendMessage::TriggerFileDownload {
|
||||||
|
document: format!(
|
||||||
|
r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="{} {} {} {}">{}{}</svg>"#,
|
||||||
|
bbox[0].x,
|
||||||
|
bbox[0].y,
|
||||||
|
size.x,
|
||||||
|
size.y,
|
||||||
|
"\n",
|
||||||
|
self.graphene_document.render_root(self.view_mode)
|
||||||
|
),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
FlipSelectedLayers(axis) => {
|
||||||
|
self.backup(responses);
|
||||||
|
let scale = match axis {
|
||||||
|
FlipAxis::X => DVec2::new(-1., 1.),
|
||||||
|
FlipAxis::Y => DVec2::new(1., -1.),
|
||||||
|
};
|
||||||
|
if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) {
|
||||||
|
let center = (max + min) / 2.;
|
||||||
|
let bbox_trans = DAffine2::from_translation(-center);
|
||||||
|
for path in self.selected_layers() {
|
||||||
|
responses.push_back(
|
||||||
|
DocumentOperation::TransformLayerInScope {
|
||||||
|
path: path.to_vec(),
|
||||||
|
transform: DAffine2::from_scale(scale).to_cols_array(),
|
||||||
|
scope: bbox_trans.to_cols_array(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FolderChanged(path) => {
|
||||||
|
let _ = self.graphene_document.render_root(self.view_mode);
|
||||||
|
responses.extend([LayerChanged(path).into(), DocumentStructureChanged.into()]);
|
||||||
|
}
|
||||||
|
GroupSelectedLayers => {
|
||||||
|
let mut new_folder_path: Vec<u64> = self.graphene_document.shallowest_common_folder(self.selected_layers()).unwrap_or(&[]).to_vec();
|
||||||
|
|
||||||
|
// Required for grouping parent folders with their own children
|
||||||
|
if !new_folder_path.is_empty() && self.selected_layers_contains(&new_folder_path) {
|
||||||
|
new_folder_path.remove(new_folder_path.len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_folder_path.push(generate_uuid());
|
||||||
|
|
||||||
|
responses.push_back(PortfolioMessage::Copy(Clipboard::System).into());
|
||||||
|
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
||||||
|
responses.push_back(DocumentOperation::CreateFolder { path: new_folder_path.clone() }.into());
|
||||||
|
responses.push_back(DocumentMessage::ToggleLayerExpansion(new_folder_path.clone()).into());
|
||||||
|
responses.push_back(
|
||||||
|
PortfolioMessage::PasteIntoFolder {
|
||||||
|
clipboard: Clipboard::System,
|
||||||
|
path: new_folder_path.clone(),
|
||||||
|
insert_index: -1,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
responses.push_back(DocumentMessage::SetSelectedLayers(vec![new_folder_path]).into());
|
||||||
|
}
|
||||||
|
LayerChanged(path) => {
|
||||||
|
if let Ok(layer_entry) = self.layer_panel_entry(path) {
|
||||||
|
responses.push_back(FrontendMessage::UpdateDocumentLayer { data: layer_entry }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Movement(message) => self.movement_handler.process_action(message, (&self.graphene_document, ipp), responses),
|
||||||
|
MoveSelectedLayersTo { path, insert_index } => {
|
||||||
|
let layers = self.selected_layers().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Trying to insert into self.
|
||||||
|
if layers.iter().any(|layer| path.starts_with(layer)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let insert_index = self.update_insert_index(&layers, &path, insert_index).unwrap();
|
||||||
|
responses.push_back(PortfolioMessage::Copy(Clipboard::System).into());
|
||||||
|
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
||||||
|
responses.push_back(
|
||||||
|
PortfolioMessage::PasteIntoFolder {
|
||||||
|
clipboard: Clipboard::System,
|
||||||
|
path,
|
||||||
|
insert_index,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
NudgeSelectedLayers(x, y) => {
|
||||||
|
self.backup(responses);
|
||||||
|
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||||
|
let operation = DocumentOperation::TransformLayerInViewport {
|
||||||
|
path,
|
||||||
|
transform: DAffine2::from_translation((x, y).into()).to_cols_array(),
|
||||||
|
};
|
||||||
|
responses.push_back(operation.into());
|
||||||
|
}
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
Overlay(message) => {
|
||||||
|
self.overlay_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());
|
||||||
|
}
|
||||||
|
Redo => {
|
||||||
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
|
responses.push_back(DocumentHistoryForward.into());
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
responses.push_back(RenderDocument.into());
|
||||||
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
|
}
|
||||||
|
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
||||||
RenderDocument => {
|
RenderDocument => {
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
FrontendMessage::UpdateDocumentArtwork {
|
FrontendMessage::UpdateDocumentArtwork {
|
||||||
@@ -909,47 +859,6 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
DirtyRenderDocument => {
|
|
||||||
// Mark all non-overlay caches as dirty
|
|
||||||
GrapheneDocument::visit_all_shapes(&mut self.graphene_document.root, &mut |_| {});
|
|
||||||
|
|
||||||
responses.push_back(DocumentMessage::RenderDocument.into());
|
|
||||||
}
|
|
||||||
DirtyRenderDocumentInOutlineView => {
|
|
||||||
if self.view_mode == ViewMode::Outline {
|
|
||||||
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NudgeSelectedLayers(x, y) => {
|
|
||||||
self.backup(responses);
|
|
||||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
|
||||||
let operation = DocumentOperation::TransformLayerInViewport {
|
|
||||||
path,
|
|
||||||
transform: DAffine2::from_translation((x, y).into()).to_cols_array(),
|
|
||||||
};
|
|
||||||
responses.push_back(operation.into());
|
|
||||||
}
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
MoveSelectedLayersTo { path, insert_index } => {
|
|
||||||
let layers = self.selected_layers().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Trying to insert into self.
|
|
||||||
if layers.iter().any(|layer| path.starts_with(layer)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let insert_index = self.update_insert_index(&layers, &path, insert_index).unwrap();
|
|
||||||
responses.push_back(PortfolioMessage::Copy(Clipboard::System).into());
|
|
||||||
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
|
|
||||||
responses.push_back(
|
|
||||||
PortfolioMessage::PasteIntoFolder {
|
|
||||||
clipboard: Clipboard::System,
|
|
||||||
path,
|
|
||||||
insert_index,
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ReorderSelectedLayers(relative_position) => {
|
ReorderSelectedLayers(relative_position) => {
|
||||||
self.backup(responses);
|
self.backup(responses);
|
||||||
let all_layer_paths = self.all_layers_sorted();
|
let all_layer_paths = self.all_layers_sorted();
|
||||||
@@ -981,69 +890,163 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FlipSelectedLayers(axis) => {
|
RollbackTransaction => {
|
||||||
self.backup(responses);
|
self.rollback(responses).unwrap_or_else(|e| log::warn!("{}", e));
|
||||||
let scale = match axis {
|
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
||||||
FlipAxis::X => DVec2::new(-1., 1.),
|
|
||||||
FlipAxis::Y => DVec2::new(1., -1.),
|
|
||||||
};
|
|
||||||
if let Some([min, max]) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) {
|
|
||||||
let center = (max + min) / 2.;
|
|
||||||
let bbox_trans = DAffine2::from_translation(-center);
|
|
||||||
for path in self.selected_layers() {
|
|
||||||
responses.push_back(
|
|
||||||
DocumentOperation::TransformLayerInScope {
|
|
||||||
path: path.to_vec(),
|
|
||||||
transform: DAffine2::from_scale(scale).to_cols_array(),
|
|
||||||
scope: bbox_trans.to_cols_array(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AlignSelectedLayers(axis, aggregate) => {
|
SaveDocument => {
|
||||||
self.backup(responses);
|
self.set_save_state(true);
|
||||||
let (paths, boxes): (Vec<_>, Vec<_>) = self
|
responses.push_back(PortfolioMessage::AutoSaveActiveDocument.into());
|
||||||
.selected_layers()
|
// Update the save status of the just saved document
|
||||||
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?.map(|b| (path, b)))
|
responses.push_back(PortfolioMessage::UpdateOpenDocumentsList.into());
|
||||||
.unzip();
|
|
||||||
|
|
||||||
let axis = match axis {
|
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
||||||
AlignAxis::X => DVec2::X,
|
true => self.name.clone(),
|
||||||
AlignAxis::Y => DVec2::Y,
|
false => self.name.clone() + FILE_SAVE_SUFFIX,
|
||||||
};
|
};
|
||||||
let lerp = |bbox: &[DVec2; 2]| bbox[0].lerp(bbox[1], 0.5);
|
responses.push_back(
|
||||||
if let Some(combined_box) = self.graphene_document.combined_viewport_bounding_box(self.selected_layers()) {
|
FrontendMessage::TriggerFileDownload {
|
||||||
let aggregated = match aggregate {
|
document: self.serialize_document(),
|
||||||
AlignAggregate::Min => combined_box[0],
|
name,
|
||||||
AlignAggregate::Max => combined_box[1],
|
}
|
||||||
AlignAggregate::Center => lerp(&combined_box),
|
.into(),
|
||||||
AlignAggregate::Average => boxes.iter().map(|b| lerp(b)).reduce(|a, b| a + b).map(|b| b / boxes.len() as f64).unwrap(),
|
)
|
||||||
};
|
}
|
||||||
for (path, bbox) in paths.into_iter().zip(boxes) {
|
SelectAllLayers => {
|
||||||
let center = match aggregate {
|
let all_layer_paths = self.all_layers();
|
||||||
AlignAggregate::Min => bbox[0],
|
responses.push_front(SetSelectedLayers(all_layer_paths.map(|path| path.to_vec()).collect()).into());
|
||||||
AlignAggregate::Max => bbox[1],
|
}
|
||||||
_ => lerp(&bbox),
|
SelectionChanged => {
|
||||||
};
|
// TODO: Hoist this duplicated code into wider system
|
||||||
let translation = (aggregated - center) * axis;
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
responses.push_back(
|
}
|
||||||
DocumentOperation::TransformLayerInViewport {
|
SelectLayer(selected, ctrl, shift) => {
|
||||||
path: path.to_vec(),
|
let mut paths = vec![];
|
||||||
transform: DAffine2::from_translation(translation).to_cols_array(),
|
let last_selection_exists = !self.layer_range_selection_reference.is_empty();
|
||||||
}
|
|
||||||
.into(),
|
// If we have shift pressed and a layer already selected then fill the range
|
||||||
);
|
if shift && last_selection_exists {
|
||||||
|
// Fill the selection range
|
||||||
|
self.layer_metadata
|
||||||
|
.iter()
|
||||||
|
.filter(|(target, _)| self.graphene_document.layer_is_between(target, &selected, &self.layer_range_selection_reference))
|
||||||
|
.for_each(|(layer_path, _)| {
|
||||||
|
paths.push(layer_path.clone());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if ctrl {
|
||||||
|
// Toggle selection when holding ctrl
|
||||||
|
let layer = self.layer_metadata_mut(&selected);
|
||||||
|
layer.selected = !layer.selected;
|
||||||
|
responses.push_back(LayerChanged(selected.clone()).into());
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
} else {
|
||||||
|
paths.push(selected.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set our last selection reference
|
||||||
|
self.layer_range_selection_reference = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't create messages for empty operations
|
||||||
|
if !paths.is_empty() {
|
||||||
|
// Add or set our selected layers
|
||||||
|
if ctrl {
|
||||||
|
responses.push_front(AddSelectedLayers(paths).into());
|
||||||
|
} else {
|
||||||
|
responses.push_front(SetSelectedLayers(paths).into());
|
||||||
}
|
}
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
SetBlendModeForSelectedLayers(blend_mode) => {
|
||||||
|
self.backup(responses);
|
||||||
|
for path in self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then(|| path.clone())) {
|
||||||
|
responses.push_back(DocumentOperation::SetLayerBlendMode { path, blend_mode }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetLayerExpansion(path, is_expanded) => {
|
||||||
|
self.layer_metadata_mut(&path).expanded = is_expanded;
|
||||||
|
responses.push_back(DocumentStructureChanged.into());
|
||||||
|
responses.push_back(LayerChanged(path).into())
|
||||||
|
}
|
||||||
|
SetOpacityForSelectedLayers(opacity) => {
|
||||||
|
self.backup(responses);
|
||||||
|
let opacity = opacity.clamp(0., 1.);
|
||||||
|
|
||||||
|
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||||
|
responses.push_back(DocumentOperation::SetLayerOpacity { path, opacity }.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetSelectedLayers(paths) => {
|
||||||
|
let selected = self.layer_metadata.iter_mut().filter(|(_, layer_metadata)| layer_metadata.selected);
|
||||||
|
selected.for_each(|(path, layer_metadata)| {
|
||||||
|
layer_metadata.selected = false;
|
||||||
|
responses.push_back(LayerChanged(path.clone()).into())
|
||||||
|
});
|
||||||
|
|
||||||
|
responses.push_front(AddSelectedLayers(paths).into());
|
||||||
|
}
|
||||||
SetSnapping(new_status) => {
|
SetSnapping(new_status) => {
|
||||||
self.snapping_enabled = new_status;
|
self.snapping_enabled = new_status;
|
||||||
}
|
}
|
||||||
|
SetViewMode(mode) => {
|
||||||
|
self.view_mode = mode;
|
||||||
|
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
||||||
|
}
|
||||||
|
StartTransaction => self.backup(responses),
|
||||||
|
ToggleLayerExpansion(path) => {
|
||||||
|
self.layer_metadata_mut(&path).expanded ^= true;
|
||||||
|
responses.push_back(DocumentStructureChanged.into());
|
||||||
|
responses.push_back(LayerChanged(path).into())
|
||||||
|
}
|
||||||
|
ToggleLayerVisibility(path) => {
|
||||||
|
responses.push_back(DocumentOperation::ToggleLayerVisibility { path }.into());
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
TransformLayers(message) => self
|
||||||
|
.transform_layer_handler
|
||||||
|
.process_action(message, (&mut self.layer_metadata, &mut self.graphene_document, ipp), responses),
|
||||||
|
Undo => {
|
||||||
|
responses.push_back(SelectMessage::Abort.into());
|
||||||
|
responses.push_back(DocumentHistoryBackward.into());
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
responses.push_back(RenderDocument.into());
|
||||||
|
responses.push_back(FolderChanged(vec![]).into());
|
||||||
|
}
|
||||||
|
UngroupLayers(folder_path) => {
|
||||||
|
// Select all the children of the folder
|
||||||
|
let to_select = self.graphene_document.folder_children_paths(&folder_path);
|
||||||
|
let message_buffer = [
|
||||||
|
// Copy them
|
||||||
|
DocumentMessage::SetSelectedLayers(to_select).into(),
|
||||||
|
PortfolioMessage::Copy(Clipboard::System).into(),
|
||||||
|
// Paste them into the folder above
|
||||||
|
PortfolioMessage::PasteIntoFolder {
|
||||||
|
clipboard: Clipboard::System,
|
||||||
|
path: folder_path[..folder_path.len() - 1].to_vec(),
|
||||||
|
insert_index: -1,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
// Delete parent folder
|
||||||
|
DocumentMessage::DeleteLayer(folder_path).into(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Push these messages in reverse due to push_front
|
||||||
|
for message in message_buffer.into_iter().rev() {
|
||||||
|
responses.push_front(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UngroupSelectedLayers => {
|
||||||
|
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||||
|
let folder_paths = self.graphene_document.sorted_folders_by_depth(self.selected_layers());
|
||||||
|
for folder_path in folder_paths {
|
||||||
|
responses.push_back(DocumentMessage::UngroupLayers(folder_path.to_vec()).into());
|
||||||
|
}
|
||||||
|
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||||
|
}
|
||||||
|
UpdateLayerMetadata { layer_path: path, layer_metadata } => {
|
||||||
|
self.layer_metadata.insert(path, layer_metadata);
|
||||||
|
}
|
||||||
ZoomCanvasToFitAll => {
|
ZoomCanvasToFitAll => {
|
||||||
if let Some(bounds) = self.document_bounds() {
|
if let Some(bounds) = self.document_bounds() {
|
||||||
responses.push_back(
|
responses.push_back(
|
||||||
|
|||||||
@@ -14,38 +14,39 @@ use glam::{DAffine2, DVec2};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, DocumentMessage, Movement)]
|
#[impl_message(Message, DocumentMessage, Movement)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum MovementMessage {
|
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 {
|
MouseMove {
|
||||||
snap_angle: Key,
|
snap_angle: Key,
|
||||||
wait_for_snap_angle_release: bool,
|
wait_for_snap_angle_release: bool,
|
||||||
snap_zoom: Key,
|
snap_zoom: Key,
|
||||||
zoom_from_viewport: Option<DVec2>,
|
zoom_from_viewport: Option<DVec2>,
|
||||||
},
|
},
|
||||||
|
RotateCanvasBegin,
|
||||||
|
SetCanvasRotation(f64),
|
||||||
|
SetCanvasZoom(f64),
|
||||||
|
TransformCanvasEnd,
|
||||||
|
TranslateCanvas(DVec2),
|
||||||
TranslateCanvasBegin,
|
TranslateCanvasBegin,
|
||||||
|
TranslateCanvasByViewportFraction(DVec2),
|
||||||
WheelCanvasTranslate {
|
WheelCanvasTranslate {
|
||||||
use_y_as_x: bool,
|
use_y_as_x: bool,
|
||||||
},
|
},
|
||||||
RotateCanvasBegin,
|
|
||||||
ZoomCanvasBegin,
|
|
||||||
TransformCanvasEnd,
|
|
||||||
SetCanvasRotation(f64),
|
|
||||||
SetCanvasZoom(f64),
|
|
||||||
IncreaseCanvasZoom {
|
|
||||||
center_on_mouse: bool,
|
|
||||||
},
|
|
||||||
DecreaseCanvasZoom {
|
|
||||||
center_on_mouse: bool,
|
|
||||||
},
|
|
||||||
WheelCanvasZoom,
|
WheelCanvasZoom,
|
||||||
FitViewportToBounds {
|
ZoomCanvasBegin,
|
||||||
bounds: [DVec2; 2],
|
|
||||||
padding_scale_factor: Option<f32>,
|
|
||||||
prevent_zoom_past_100: bool,
|
|
||||||
},
|
|
||||||
TranslateCanvas(DVec2),
|
|
||||||
TranslateCanvasByViewportFraction(DVec2),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
@@ -148,32 +149,54 @@ impl MovementMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for MovementMessageHandler {
|
impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> 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, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||||
let (document, ipp) = data;
|
let (document, ipp) = data;
|
||||||
use MovementMessage::*;
|
use MovementMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
TranslateCanvasBegin => {
|
DecreaseCanvasZoom { center_on_mouse } => {
|
||||||
self.panning = true;
|
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < self.zoom).unwrap_or(&self.zoom);
|
||||||
self.mouse_position = ipp.mouse.position;
|
if center_on_mouse {
|
||||||
|
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
||||||
|
}
|
||||||
|
responses.push_back(SetCanvasZoom(new_scale).into());
|
||||||
}
|
}
|
||||||
RotateCanvasBegin => {
|
FitViewportToBounds {
|
||||||
self.tilting = true;
|
bounds: [bounds_corner_a, bounds_corner_b],
|
||||||
self.mouse_position = ipp.mouse.position;
|
padding_scale_factor,
|
||||||
}
|
prevent_zoom_past_100,
|
||||||
ZoomCanvasBegin => {
|
} => {
|
||||||
self.zooming = true;
|
let pos1 = document.root.transform.inverse().transform_point2(bounds_corner_a);
|
||||||
self.mouse_position = ipp.mouse.position;
|
let pos2 = document.root.transform.inverse().transform_point2(bounds_corner_b);
|
||||||
}
|
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
|
||||||
TransformCanvasEnd => {
|
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());
|
||||||
self.tilt = self.snapped_angle();
|
|
||||||
self.zoom = self.snapped_scale();
|
let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
|
||||||
|
let size = (pos2 - pos1) / (v2 - v1);
|
||||||
|
let size = 1. / size;
|
||||||
|
let new_scale = size.min_element();
|
||||||
|
|
||||||
|
self.pan += center;
|
||||||
|
self.zoom *= new_scale;
|
||||||
|
|
||||||
|
self.zoom /= padding_scale_factor.unwrap_or(1.) as f64;
|
||||||
|
|
||||||
|
if self.zoom > 1. && prevent_zoom_past_100 {
|
||||||
|
self.zoom = 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.zoom }.into());
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
self.snap_tilt = false;
|
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||||
self.snap_tilt_released = false;
|
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||||
self.snap_zoom = false;
|
}
|
||||||
self.panning = false;
|
IncreaseCanvasZoom { center_on_mouse } => {
|
||||||
self.tilting = false;
|
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > self.zoom).unwrap_or(&self.zoom);
|
||||||
self.zooming = false;
|
if center_on_mouse {
|
||||||
|
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
||||||
|
}
|
||||||
|
responses.push_back(SetCanvasZoom(new_scale).into());
|
||||||
}
|
}
|
||||||
MouseMove {
|
MouseMove {
|
||||||
snap_angle,
|
snap_angle,
|
||||||
@@ -233,6 +256,16 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||||||
}
|
}
|
||||||
self.mouse_position = ipp.mouse.position;
|
self.mouse_position = ipp.mouse.position;
|
||||||
}
|
}
|
||||||
|
RotateCanvasBegin => {
|
||||||
|
self.tilting = true;
|
||||||
|
self.mouse_position = ipp.mouse.position;
|
||||||
|
}
|
||||||
|
SetCanvasRotation(new_radians) => {
|
||||||
|
self.tilt = new_radians;
|
||||||
|
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
responses.push_back(FrontendMessage::UpdateCanvasRotation { angle_radians: self.snapped_angle() }.into());
|
||||||
|
}
|
||||||
SetCanvasZoom(new) => {
|
SetCanvasZoom(new) => {
|
||||||
self.zoom = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
self.zoom = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||||
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.snapped_scale() }.into());
|
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.snapped_scale() }.into());
|
||||||
@@ -240,19 +273,41 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||||
}
|
}
|
||||||
IncreaseCanvasZoom { center_on_mouse } => {
|
TransformCanvasEnd => {
|
||||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > self.zoom).unwrap_or(&self.zoom);
|
self.tilt = self.snapped_angle();
|
||||||
if center_on_mouse {
|
self.zoom = self.snapped_scale();
|
||||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
}
|
self.snap_tilt = false;
|
||||||
responses.push_back(SetCanvasZoom(new_scale).into());
|
self.snap_tilt_released = false;
|
||||||
|
self.snap_zoom = false;
|
||||||
|
self.panning = false;
|
||||||
|
self.tilting = false;
|
||||||
|
self.zooming = false;
|
||||||
}
|
}
|
||||||
DecreaseCanvasZoom { center_on_mouse } => {
|
TranslateCanvas(delta) => {
|
||||||
let new_scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < self.zoom).unwrap_or(&self.zoom);
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||||
if center_on_mouse {
|
|
||||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), new_scale / self.zoom, ipp.mouse.position));
|
self.pan += transformed_delta;
|
||||||
}
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
responses.push_back(SetCanvasZoom(new_scale).into());
|
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||||
|
}
|
||||||
|
TranslateCanvasBegin => {
|
||||||
|
self.panning = true;
|
||||||
|
self.mouse_position = ipp.mouse.position;
|
||||||
|
}
|
||||||
|
TranslateCanvasByViewportFraction(delta) => {
|
||||||
|
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||||
|
|
||||||
|
self.pan += transformed_delta;
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
self.create_document_transform(&ipp.viewport_bounds, responses);
|
||||||
|
}
|
||||||
|
WheelCanvasTranslate { use_y_as_x } => {
|
||||||
|
let delta = match use_y_as_x {
|
||||||
|
false => -ipp.mouse.scroll_delta.as_dvec2(),
|
||||||
|
true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(),
|
||||||
|
} * VIEWPORT_SCROLL_RATE;
|
||||||
|
responses.push_back(TranslateCanvas(delta).into());
|
||||||
}
|
}
|
||||||
WheelCanvasZoom => {
|
WheelCanvasZoom => {
|
||||||
let scroll = ipp.mouse.scroll_delta.scroll_delta();
|
let scroll = ipp.mouse.scroll_delta.scroll_delta();
|
||||||
@@ -264,61 +319,9 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessor)> for Moveme
|
|||||||
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
|
responses.push_back(self.center_zoom(ipp.viewport_bounds.size(), zoom_factor, ipp.mouse.position));
|
||||||
responses.push_back(SetCanvasZoom(self.zoom * zoom_factor).into());
|
responses.push_back(SetCanvasZoom(self.zoom * zoom_factor).into());
|
||||||
}
|
}
|
||||||
WheelCanvasTranslate { use_y_as_x } => {
|
ZoomCanvasBegin => {
|
||||||
let delta = match use_y_as_x {
|
self.zooming = true;
|
||||||
false => -ipp.mouse.scroll_delta.as_dvec2(),
|
self.mouse_position = ipp.mouse.position;
|
||||||
true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(),
|
|
||||||
} * VIEWPORT_SCROLL_RATE;
|
|
||||||
responses.push_back(TranslateCanvas(delta).into());
|
|
||||||
}
|
|
||||||
SetCanvasRotation(new_radians) => {
|
|
||||||
self.tilt = new_radians;
|
|
||||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
responses.push_back(FrontendMessage::UpdateCanvasRotation { angle_radians: self.snapped_angle() }.into());
|
|
||||||
}
|
|
||||||
FitViewportToBounds {
|
|
||||||
bounds: [bounds_corner_a, bounds_corner_b],
|
|
||||||
padding_scale_factor,
|
|
||||||
prevent_zoom_past_100,
|
|
||||||
} => {
|
|
||||||
let pos1 = document.root.transform.inverse().transform_point2(bounds_corner_a);
|
|
||||||
let pos2 = document.root.transform.inverse().transform_point2(bounds_corner_b);
|
|
||||||
let v1 = document.root.transform.inverse().transform_point2(DVec2::ZERO);
|
|
||||||
let v2 = document.root.transform.inverse().transform_point2(ipp.viewport_bounds.size());
|
|
||||||
|
|
||||||
let center = v1.lerp(v2, 0.5) - pos1.lerp(pos2, 0.5);
|
|
||||||
let size = (pos2 - pos1) / (v2 - v1);
|
|
||||||
let size = 1. / size;
|
|
||||||
let new_scale = size.min_element();
|
|
||||||
|
|
||||||
self.pan += center;
|
|
||||||
self.zoom *= new_scale;
|
|
||||||
|
|
||||||
self.zoom /= padding_scale_factor.unwrap_or(1.) as f64;
|
|
||||||
|
|
||||||
if self.zoom > 1. && prevent_zoom_past_100 {
|
|
||||||
self.zoom = 1.
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.push_back(FrontendMessage::UpdateCanvasZoom { factor: self.zoom }.into());
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
|
||||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
|
||||||
}
|
|
||||||
TranslateCanvas(delta) => {
|
|
||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
|
||||||
|
|
||||||
self.pan += transformed_delta;
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
|
||||||
}
|
|
||||||
TranslateCanvasByViewportFraction(delta) => {
|
|
||||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
|
||||||
|
|
||||||
self.pan += transformed_delta;
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
self.create_document_transform(&ipp.viewport_bounds, responses);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ use graphene::document::Document as GrapheneDocument;
|
|||||||
use graphene::layers::style::ViewMode;
|
use graphene::layers::style::ViewMode;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, DocumentMessage, Overlay)]
|
#[impl_message(Message, DocumentMessage, Overlay)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum OverlayMessage {
|
pub enum OverlayMessage {
|
||||||
DispatchOperation(Box<DocumentOperation>),
|
|
||||||
ClearAllOverlays,
|
ClearAllOverlays,
|
||||||
|
DispatchOperation(Box<DocumentOperation>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DocumentOperation> for OverlayMessage {
|
impl From<DocumentOperation> for OverlayMessage {
|
||||||
@@ -28,15 +29,17 @@ pub struct OverlayMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<OverlayMessage, (&mut LayerMetadata, &Document, &InputPreprocessor)> for OverlayMessageHandler {
|
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>) {
|
fn process_action(&mut self, message: OverlayMessage, _data: (&mut LayerMetadata, &Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||||
// let (layer_metadata, document, ipp) = data;
|
// let (layer_metadata, document, ipp) = data;
|
||||||
use OverlayMessage::*;
|
use OverlayMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
|
ClearAllOverlays => todo!(),
|
||||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => log::error!("OverlayError: {:?}", e),
|
Err(e) => log::error!("OverlayError: {:?}", e),
|
||||||
},
|
},
|
||||||
ClearAllOverlays => todo!(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render overlays
|
// Render overlays
|
||||||
|
|||||||
@@ -21,40 +21,41 @@ pub enum Clipboard {
|
|||||||
|
|
||||||
const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
const CLIPBOARD_COUNT: u8 = Clipboard::_ClipboardCount as u8;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Portfolio)]
|
#[impl_message(Message, Portfolio)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum PortfolioMessage {
|
pub enum PortfolioMessage {
|
||||||
|
AutoSaveActiveDocument,
|
||||||
|
AutoSaveDocument(u64),
|
||||||
|
CloseActiveDocumentWithConfirmation,
|
||||||
|
CloseAllDocuments,
|
||||||
|
CloseAllDocumentsWithConfirmation,
|
||||||
|
CloseDocument(u64),
|
||||||
|
CloseDocumentWithConfirmation(u64),
|
||||||
Copy(Clipboard),
|
Copy(Clipboard),
|
||||||
Cut(Clipboard),
|
Cut(Clipboard),
|
||||||
PasteIntoFolder {
|
|
||||||
clipboard: Clipboard,
|
|
||||||
path: Vec<LayerId>,
|
|
||||||
insert_index: isize,
|
|
||||||
},
|
|
||||||
Paste(Clipboard),
|
|
||||||
SelectDocument(u64),
|
|
||||||
CloseDocument(u64),
|
|
||||||
#[child]
|
#[child]
|
||||||
Document(DocumentMessage),
|
Document(DocumentMessage),
|
||||||
CloseActiveDocumentWithConfirmation,
|
|
||||||
CloseDocumentWithConfirmation(u64),
|
|
||||||
CloseAllDocumentsWithConfirmation,
|
|
||||||
CloseAllDocuments,
|
|
||||||
RequestAboutGraphiteDialog,
|
|
||||||
NewDocument,
|
NewDocument,
|
||||||
|
NextDocument,
|
||||||
OpenDocument,
|
OpenDocument,
|
||||||
|
OpenDocumentFile(String, String),
|
||||||
OpenDocumentFileWithId {
|
OpenDocumentFileWithId {
|
||||||
document: String,
|
document: String,
|
||||||
document_name: String,
|
document_name: String,
|
||||||
document_id: u64,
|
document_id: u64,
|
||||||
document_is_saved: bool,
|
document_is_saved: bool,
|
||||||
},
|
},
|
||||||
OpenDocumentFile(String, String),
|
Paste(Clipboard),
|
||||||
UpdateOpenDocumentsList,
|
PasteIntoFolder {
|
||||||
AutoSaveDocument(u64),
|
clipboard: Clipboard,
|
||||||
AutoSaveActiveDocument,
|
path: Vec<LayerId>,
|
||||||
NextDocument,
|
insert_index: isize,
|
||||||
|
},
|
||||||
PrevDocument,
|
PrevDocument,
|
||||||
|
RequestAboutGraphiteDialog,
|
||||||
|
SelectDocument(u64),
|
||||||
|
UpdateOpenDocumentsList,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -176,44 +177,31 @@ impl Default for PortfolioMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHandler {
|
impl MessageHandler<PortfolioMessage, &InputPreprocessor> 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: &InputPreprocessor, responses: &mut VecDeque<Message>) {
|
||||||
use DocumentMessage::*;
|
use DocumentMessage::*;
|
||||||
use PortfolioMessage::*;
|
use PortfolioMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
RequestAboutGraphiteDialog => {
|
AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument(self.active_document_id).into()),
|
||||||
responses.push_back(FrontendMessage::DisplayDialogAboutGraphite.into());
|
AutoSaveDocument(id) => {
|
||||||
}
|
let document = self.documents.get(&id).unwrap();
|
||||||
Document(message) => self.active_document_mut().process_action(message, ipp, responses),
|
responses.push_back(
|
||||||
SelectDocument(id) => {
|
FrontendMessage::TriggerIndexedDbWriteDocument {
|
||||||
let active_document = self.active_document();
|
document: document.serialize_document(),
|
||||||
if !active_document.is_saved() {
|
details: FrontendDocumentDetails {
|
||||||
responses.push_back(PortfolioMessage::AutoSaveDocument(self.active_document_id).into());
|
is_saved: document.is_saved(),
|
||||||
}
|
id,
|
||||||
self.active_document_id = id;
|
name: document.name.clone(),
|
||||||
responses.push_back(FrontendMessage::UpdateActiveDocument { document_id: id }.into());
|
},
|
||||||
responses.push_back(RenderDocument.into());
|
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
|
||||||
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
}
|
||||||
for layer in self.active_document().layer_metadata.keys() {
|
.into(),
|
||||||
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
|
)
|
||||||
}
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
}
|
||||||
CloseActiveDocumentWithConfirmation => {
|
CloseActiveDocumentWithConfirmation => {
|
||||||
responses.push_back(PortfolioMessage::CloseDocumentWithConfirmation(self.active_document_id).into());
|
responses.push_back(PortfolioMessage::CloseDocumentWithConfirmation(self.active_document_id).into());
|
||||||
}
|
}
|
||||||
CloseDocumentWithConfirmation(id) => {
|
|
||||||
let target_document = self.documents.get(&id).unwrap();
|
|
||||||
if target_document.is_saved() {
|
|
||||||
responses.push_back(PortfolioMessage::CloseDocument(id).into());
|
|
||||||
} else {
|
|
||||||
responses.push_back(FrontendMessage::DisplayConfirmationToCloseDocument { document_id: id }.into());
|
|
||||||
// Select the document being closed
|
|
||||||
responses.push_back(PortfolioMessage::SelectDocument(id).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CloseAllDocumentsWithConfirmation => {
|
|
||||||
responses.push_back(FrontendMessage::DisplayConfirmationToCloseAllDocuments.into());
|
|
||||||
}
|
|
||||||
CloseAllDocuments => {
|
CloseAllDocuments => {
|
||||||
// Empty the list of internal document data
|
// Empty the list of internal document data
|
||||||
self.documents.clear();
|
self.documents.clear();
|
||||||
@@ -222,6 +210,9 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||||||
// Create a new blank document
|
// Create a new blank document
|
||||||
responses.push_back(NewDocument.into());
|
responses.push_back(NewDocument.into());
|
||||||
}
|
}
|
||||||
|
CloseAllDocumentsWithConfirmation => {
|
||||||
|
responses.push_back(FrontendMessage::DisplayConfirmationToCloseAllDocuments.into());
|
||||||
|
}
|
||||||
CloseDocument(id) => {
|
CloseDocument(id) => {
|
||||||
let document_index = self.document_index(id);
|
let document_index = self.document_index(id);
|
||||||
self.documents.remove(&id);
|
self.documents.remove(&id);
|
||||||
@@ -267,6 +258,37 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||||||
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
|
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CloseDocumentWithConfirmation(id) => {
|
||||||
|
let target_document = self.documents.get(&id).unwrap();
|
||||||
|
if target_document.is_saved() {
|
||||||
|
responses.push_back(PortfolioMessage::CloseDocument(id).into());
|
||||||
|
} else {
|
||||||
|
responses.push_back(FrontendMessage::DisplayConfirmationToCloseDocument { document_id: id }.into());
|
||||||
|
// Select the document being closed
|
||||||
|
responses.push_back(PortfolioMessage::SelectDocument(id).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Copy(clipboard) => {
|
||||||
|
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
||||||
|
let active_document = self.documents.get(&self.active_document_id).unwrap();
|
||||||
|
|
||||||
|
let copy_buffer = &mut self.copy_buffer;
|
||||||
|
copy_buffer[clipboard as usize].clear();
|
||||||
|
|
||||||
|
for layer_path in active_document.selected_layers_without_children() {
|
||||||
|
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
|
||||||
|
(Ok(layer), layer_metadata) => {
|
||||||
|
copy_buffer[clipboard as usize].push(CopyBufferEntry { layer, layer_metadata });
|
||||||
|
}
|
||||||
|
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Cut(clipboard) => {
|
||||||
|
responses.push_back(Copy(clipboard).into());
|
||||||
|
responses.push_back(DeleteSelectedLayers.into());
|
||||||
|
}
|
||||||
|
Document(message) => self.active_document_mut().process_action(message, ipp, responses),
|
||||||
NewDocument => {
|
NewDocument => {
|
||||||
let name = self.generate_new_document_name();
|
let name = self.generate_new_document_name();
|
||||||
let new_document = DocumentMessageHandler::with_name(name, ipp);
|
let new_document = DocumentMessageHandler::with_name(name, ipp);
|
||||||
@@ -274,6 +296,12 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||||||
self.active_document_id = document_id;
|
self.active_document_id = document_id;
|
||||||
self.load_document(new_document, document_id, false, responses);
|
self.load_document(new_document, document_id, false, responses);
|
||||||
}
|
}
|
||||||
|
NextDocument => {
|
||||||
|
let current_index = self.document_index(self.active_document_id);
|
||||||
|
let next_index = (current_index + 1) % self.document_ids.len();
|
||||||
|
let next_id = self.document_ids[next_index];
|
||||||
|
responses.push_back(PortfolioMessage::SelectDocument(next_id).into());
|
||||||
|
}
|
||||||
OpenDocument => {
|
OpenDocument => {
|
||||||
responses.push_back(FrontendMessage::TriggerFileUpload.into());
|
responses.push_back(FrontendMessage::TriggerFileUpload.into());
|
||||||
}
|
}
|
||||||
@@ -309,70 +337,6 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateOpenDocumentsList => {
|
|
||||||
// Send the list of document tab names
|
|
||||||
let open_documents = self
|
|
||||||
.document_ids
|
|
||||||
.iter()
|
|
||||||
.filter_map(|id| {
|
|
||||||
self.documents.get(id).map(|doc| FrontendDocumentDetails {
|
|
||||||
is_saved: doc.is_saved(),
|
|
||||||
id: *id,
|
|
||||||
name: doc.name.clone(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
|
||||||
}
|
|
||||||
AutoSaveDocument(id) => {
|
|
||||||
let document = self.documents.get(&id).unwrap();
|
|
||||||
responses.push_back(
|
|
||||||
FrontendMessage::TriggerIndexedDbWriteDocument {
|
|
||||||
document: document.serialize_document(),
|
|
||||||
details: FrontendDocumentDetails {
|
|
||||||
is_saved: document.is_saved(),
|
|
||||||
id,
|
|
||||||
name: document.name.clone(),
|
|
||||||
},
|
|
||||||
version: GRAPHITE_DOCUMENT_VERSION.to_string(),
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
AutoSaveActiveDocument => responses.push_back(PortfolioMessage::AutoSaveDocument(self.active_document_id).into()),
|
|
||||||
NextDocument => {
|
|
||||||
let current_index = self.document_index(self.active_document_id);
|
|
||||||
let next_index = (current_index + 1) % self.document_ids.len();
|
|
||||||
let next_id = self.document_ids[next_index];
|
|
||||||
responses.push_back(PortfolioMessage::SelectDocument(next_id).into());
|
|
||||||
}
|
|
||||||
PrevDocument => {
|
|
||||||
let len = self.document_ids.len();
|
|
||||||
let current_index = self.document_index(self.active_document_id);
|
|
||||||
let prev_index = (current_index + len - 1) % len;
|
|
||||||
let prev_id = self.document_ids[prev_index];
|
|
||||||
responses.push_back(PortfolioMessage::SelectDocument(prev_id).into());
|
|
||||||
}
|
|
||||||
Copy(clipboard) => {
|
|
||||||
// We can't use `self.active_document()` because it counts as an immutable borrow of the entirety of `self`
|
|
||||||
let active_document = self.documents.get(&self.active_document_id).unwrap();
|
|
||||||
|
|
||||||
let copy_buffer = &mut self.copy_buffer;
|
|
||||||
copy_buffer[clipboard as usize].clear();
|
|
||||||
|
|
||||||
for layer_path in active_document.selected_layers_without_children() {
|
|
||||||
match (active_document.graphene_document.layer(layer_path).map(|t| t.clone()), *active_document.layer_metadata(layer_path)) {
|
|
||||||
(Ok(layer), layer_metadata) => {
|
|
||||||
copy_buffer[clipboard as usize].push(CopyBufferEntry { layer, layer_metadata });
|
|
||||||
}
|
|
||||||
(Err(e), _) => warn!("Could not access selected layer {:?}: {:?}", layer_path, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Cut(clipboard) => {
|
|
||||||
responses.push_back(Copy(clipboard).into());
|
|
||||||
responses.push_back(DeleteSelectedLayers.into());
|
|
||||||
}
|
|
||||||
Paste(clipboard) => {
|
Paste(clipboard) => {
|
||||||
let document = self.active_document();
|
let document = self.active_document();
|
||||||
let shallowest_common_folder = document
|
let shallowest_common_folder = document
|
||||||
@@ -424,6 +388,45 @@ impl MessageHandler<PortfolioMessage, &InputPreprocessor> for PortfolioMessageHa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PrevDocument => {
|
||||||
|
let len = self.document_ids.len();
|
||||||
|
let current_index = self.document_index(self.active_document_id);
|
||||||
|
let prev_index = (current_index + len - 1) % len;
|
||||||
|
let prev_id = self.document_ids[prev_index];
|
||||||
|
responses.push_back(PortfolioMessage::SelectDocument(prev_id).into());
|
||||||
|
}
|
||||||
|
RequestAboutGraphiteDialog => {
|
||||||
|
responses.push_back(FrontendMessage::DisplayDialogAboutGraphite.into());
|
||||||
|
}
|
||||||
|
SelectDocument(id) => {
|
||||||
|
let active_document = self.active_document();
|
||||||
|
if !active_document.is_saved() {
|
||||||
|
responses.push_back(PortfolioMessage::AutoSaveDocument(self.active_document_id).into());
|
||||||
|
}
|
||||||
|
self.active_document_id = id;
|
||||||
|
responses.push_back(FrontendMessage::UpdateActiveDocument { document_id: id }.into());
|
||||||
|
responses.push_back(RenderDocument.into());
|
||||||
|
responses.push_back(DocumentMessage::DocumentStructureChanged.into());
|
||||||
|
for layer in self.active_document().layer_metadata.keys() {
|
||||||
|
responses.push_back(DocumentMessage::LayerChanged(layer.clone()).into());
|
||||||
|
}
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
|
UpdateOpenDocumentsList => {
|
||||||
|
// Send the list of document tab names
|
||||||
|
let open_documents = self
|
||||||
|
.document_ids
|
||||||
|
.iter()
|
||||||
|
.filter_map(|id| {
|
||||||
|
self.documents.get(id).map(|doc| FrontendDocumentDetails {
|
||||||
|
is_saved: doc.is_saved(),
|
||||||
|
id: *id,
|
||||||
|
name: doc.name.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
responses.push_back(FrontendMessage::UpdateOpenDocumentsList { open_documents }.into());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
|
|||||||
@@ -358,25 +358,22 @@ impl Typing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum TransformLayerMessage {
|
pub enum TransformLayerMessage {
|
||||||
BeginGrab,
|
|
||||||
BeginScale,
|
|
||||||
BeginRotate,
|
|
||||||
|
|
||||||
CancelOperation,
|
|
||||||
ApplyOperation,
|
ApplyOperation,
|
||||||
|
BeginGrab,
|
||||||
TypeNumber(u8),
|
BeginRotate,
|
||||||
|
BeginScale,
|
||||||
|
CancelOperation,
|
||||||
|
ConstrainX,
|
||||||
|
ConstrainY,
|
||||||
|
MouseMove { slow_key: Key, snap_key: Key },
|
||||||
TypeBackspace,
|
TypeBackspace,
|
||||||
TypeDecimalPoint,
|
TypeDecimalPoint,
|
||||||
TypeNegate,
|
TypeNegate,
|
||||||
|
TypeNumber(u8),
|
||||||
ConstrainX,
|
|
||||||
ConstrainY,
|
|
||||||
|
|
||||||
MouseMove { slow_key: Key, snap_key: Key },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq)]
|
#[derive(Debug, Clone, Default, PartialEq)]
|
||||||
@@ -395,6 +392,7 @@ pub struct TransformLayerMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor)> for TransformLayerMessageHandler {
|
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>) {
|
fn process_action(&mut self, message: TransformLayerMessage, data: (&mut HashMap<Vec<LayerId>, LayerMetadata>, &mut Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||||
use TransformLayerMessage::*;
|
use TransformLayerMessage::*;
|
||||||
|
|
||||||
@@ -413,7 +411,16 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
|
|||||||
*start_mouse = ipp.mouse.position;
|
*start_mouse = ipp.mouse.position;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
|
ApplyOperation => {
|
||||||
|
self.original_transforms.clear();
|
||||||
|
self.typing.clear();
|
||||||
|
|
||||||
|
self.operation = Operation::None;
|
||||||
|
|
||||||
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
|
}
|
||||||
BeginGrab => {
|
BeginGrab => {
|
||||||
if let Operation::Grabbing(_) = self.operation {
|
if let Operation::Grabbing(_) = self.operation {
|
||||||
return;
|
return;
|
||||||
@@ -458,14 +465,8 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
|
|||||||
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||||
}
|
}
|
||||||
ApplyOperation => {
|
ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||||
self.original_transforms.clear();
|
ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||||
self.typing.clear();
|
|
||||||
|
|
||||||
self.operation = Operation::None;
|
|
||||||
|
|
||||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
|
||||||
}
|
|
||||||
MouseMove { slow_key, snap_key } => {
|
MouseMove { slow_key, snap_key } => {
|
||||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||||
|
|
||||||
@@ -515,12 +516,10 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
|
|||||||
}
|
}
|
||||||
self.mouse_position = ipp.mouse.position;
|
self.mouse_position = ipp.mouse.position;
|
||||||
}
|
}
|
||||||
TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
|
||||||
TypeBackspace => self.operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
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),
|
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),
|
TypeNegate => self.operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||||
ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||||
ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,33 +12,36 @@ pub struct FrontendDocumentDetails {
|
|||||||
pub id: u64,
|
pub id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
// Display prefix: make the frontend show something, like a dialog
|
// Display prefix: make the frontend show something, like a dialog
|
||||||
// Update prefix: give the frontend a new value or state for it to use
|
DisplayConfirmationToCloseAllDocuments,
|
||||||
// Trigger prefix: cause a browser API to do something
|
DisplayConfirmationToCloseDocument { document_id: u64 },
|
||||||
DisplayDocumentLayerTreeStructure { data_buffer: RawBuffer },
|
DisplayDialogAboutGraphite,
|
||||||
UpdateActiveTool { tool_name: String, tool_options: Option<ToolOptions> },
|
|
||||||
UpdateActiveDocument { document_id: u64 },
|
|
||||||
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },
|
|
||||||
UpdateInputHints { hint_data: HintData },
|
|
||||||
DisplayDialogError { title: String, description: String },
|
DisplayDialogError { title: String, description: String },
|
||||||
DisplayDialogPanic { panic_info: String, title: String, description: String },
|
DisplayDialogPanic { panic_info: String, title: String, description: String },
|
||||||
DisplayConfirmationToCloseDocument { document_id: u64 },
|
DisplayDocumentLayerTreeStructure { data_buffer: RawBuffer },
|
||||||
DisplayConfirmationToCloseAllDocuments,
|
|
||||||
DisplayDialogAboutGraphite,
|
// Trigger prefix: cause a browser API to do something
|
||||||
UpdateDocumentLayer { data: LayerPanelEntry },
|
|
||||||
UpdateDocumentArtwork { svg: String },
|
|
||||||
UpdateDocumentOverlays { svg: String },
|
|
||||||
UpdateDocumentArtboards { svg: String },
|
|
||||||
UpdateDocumentScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
|
|
||||||
UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 },
|
|
||||||
TriggerFileUpload,
|
|
||||||
TriggerFileDownload { document: String, name: String },
|
TriggerFileDownload { document: String, name: String },
|
||||||
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },
|
TriggerFileUpload,
|
||||||
TriggerIndexedDbRemoveDocument { document_id: u64 },
|
TriggerIndexedDbRemoveDocument { document_id: u64 },
|
||||||
UpdateWorkingColors { primary: Color, secondary: Color },
|
TriggerIndexedDbWriteDocument { document: String, details: FrontendDocumentDetails, version: String },
|
||||||
UpdateCanvasZoom { factor: f64 },
|
|
||||||
|
// Update prefix: give the frontend a new value or state for it to use
|
||||||
|
UpdateActiveDocument { document_id: u64 },
|
||||||
|
UpdateActiveTool { tool_name: String, tool_options: Option<ToolOptions> },
|
||||||
UpdateCanvasRotation { angle_radians: f64 },
|
UpdateCanvasRotation { angle_radians: f64 },
|
||||||
|
UpdateCanvasZoom { factor: f64 },
|
||||||
|
UpdateDocumentArtboards { svg: String },
|
||||||
|
UpdateDocumentArtwork { svg: String },
|
||||||
|
UpdateDocumentLayer { data: LayerPanelEntry },
|
||||||
|
UpdateDocumentOverlays { svg: String },
|
||||||
|
UpdateDocumentRulers { origin: (f64, f64), spacing: f64, interval: f64 },
|
||||||
|
UpdateDocumentScrollbars { position: (f64, f64), size: (f64, f64), multiplier: (f64, f64) },
|
||||||
|
UpdateInputHints { hint_data: HintData },
|
||||||
|
UpdateOpenDocumentsList { open_documents: Vec<FrontendDocumentDetails> },
|
||||||
|
UpdateWorkingColors { primary: Color, secondary: Color },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ use crate::message_prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Global)]
|
#[impl_message(Message, Global)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum GlobalMessage {
|
pub enum GlobalMessage {
|
||||||
LogInfo,
|
|
||||||
LogDebug,
|
LogDebug,
|
||||||
|
LogInfo,
|
||||||
LogTrace,
|
LogTrace,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,17 +15,19 @@ pub enum GlobalMessage {
|
|||||||
pub struct GlobalMessageHandler {}
|
pub struct GlobalMessageHandler {}
|
||||||
|
|
||||||
impl MessageHandler<GlobalMessage, ()> for GlobalMessageHandler {
|
impl MessageHandler<GlobalMessage, ()> for GlobalMessageHandler {
|
||||||
|
#[remain::check]
|
||||||
fn process_action(&mut self, message: GlobalMessage, _data: (), _responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, message: GlobalMessage, _data: (), _responses: &mut VecDeque<Message>) {
|
||||||
use GlobalMessage::*;
|
use GlobalMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
LogInfo => {
|
|
||||||
log::set_max_level(log::LevelFilter::Info);
|
|
||||||
log::info!("set log verbosity to info");
|
|
||||||
}
|
|
||||||
LogDebug => {
|
LogDebug => {
|
||||||
log::set_max_level(log::LevelFilter::Debug);
|
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");
|
||||||
|
}
|
||||||
LogTrace => {
|
LogTrace => {
|
||||||
log::set_max_level(log::LevelFilter::Trace);
|
log::set_max_level(log::LevelFilter::Trace);
|
||||||
log::info!("set log verbosity to trace");
|
log::info!("set log verbosity to trace");
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ use std::fmt::Write;
|
|||||||
const NUDGE_AMOUNT: f64 = 1.;
|
const NUDGE_AMOUNT: f64 = 1.;
|
||||||
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
const SHIFT_NUDGE_AMOUNT: f64 = 10.;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, InputMapper)]
|
#[impl_message(Message, InputMapper)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum InputMapperMessage {
|
pub enum InputMapperMessage {
|
||||||
PointerMove,
|
|
||||||
MouseScroll,
|
|
||||||
#[child]
|
|
||||||
KeyUp(Key),
|
|
||||||
#[child]
|
#[child]
|
||||||
KeyDown(Key),
|
KeyDown(Key),
|
||||||
|
#[child]
|
||||||
|
KeyUp(Key),
|
||||||
|
MouseScroll,
|
||||||
|
PointerMove,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
@@ -116,8 +117,8 @@ macro_rules! mapping {
|
|||||||
let arr = match entry.trigger {
|
let arr = match entry.trigger {
|
||||||
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
InputMapperMessage::KeyDown(key) => &mut key_down[key as usize],
|
||||||
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
InputMapperMessage::KeyUp(key) => &mut key_up[key as usize],
|
||||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
|
||||||
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
InputMapperMessage::MouseScroll => &mut mouse_scroll,
|
||||||
|
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||||
};
|
};
|
||||||
arr.push(entry.clone());
|
arr.push(entry.clone());
|
||||||
}
|
}
|
||||||
@@ -341,8 +342,8 @@ impl Mapping {
|
|||||||
let list = match message {
|
let list = match message {
|
||||||
KeyDown(key) => &self.key_down[key as usize],
|
KeyDown(key) => &self.key_down[key as usize],
|
||||||
KeyUp(key) => &self.key_up[key as usize],
|
KeyUp(key) => &self.key_up[key as usize],
|
||||||
PointerMove => &self.pointer_move,
|
|
||||||
MouseScroll => &self.mouse_scroll,
|
MouseScroll => &self.mouse_scroll,
|
||||||
|
PointerMove => &self.pointer_move,
|
||||||
};
|
};
|
||||||
list.match_mapping(keys, actions)
|
list.match_mapping(keys, actions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ use bitflags::bitflags;
|
|||||||
pub use graphene::DocumentResponse;
|
pub use graphene::DocumentResponse;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, InputPreprocessor)]
|
#[impl_message(Message, InputPreprocessor)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum InputPreprocessorMessage {
|
pub enum InputPreprocessorMessage {
|
||||||
|
BoundsOfViewports(Vec<ViewportBounds>),
|
||||||
|
KeyDown(Key, ModifierKeys),
|
||||||
|
KeyUp(Key, ModifierKeys),
|
||||||
MouseDown(EditorMouseState, ModifierKeys),
|
MouseDown(EditorMouseState, ModifierKeys),
|
||||||
MouseUp(EditorMouseState, ModifierKeys),
|
|
||||||
MouseMove(EditorMouseState, ModifierKeys),
|
MouseMove(EditorMouseState, ModifierKeys),
|
||||||
MouseScroll(EditorMouseState, ModifierKeys),
|
MouseScroll(EditorMouseState, ModifierKeys),
|
||||||
KeyUp(Key, ModifierKeys),
|
MouseUp(EditorMouseState, ModifierKeys),
|
||||||
KeyDown(Key, ModifierKeys),
|
|
||||||
BoundsOfViewports(Vec<ViewportBounds>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
@@ -44,55 +45,10 @@ enum KeyPosition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
||||||
|
#[remain::check]
|
||||||
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, message: InputPreprocessorMessage, _data: (), responses: &mut VecDeque<Message>) {
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match 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::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::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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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::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::BoundsOfViewports(bounds_of_viewports) => {
|
InputPreprocessorMessage::BoundsOfViewports(bounds_of_viewports) => {
|
||||||
assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported");
|
assert_eq!(bounds_of_viewports.len(), 1, "Only one viewport is currently supported");
|
||||||
|
|
||||||
@@ -134,6 +90,53 @@ impl MessageHandler<InputPreprocessorMessage, ()> for InputPreprocessor {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
// clean user input and if possible reconstruct it
|
||||||
|
|||||||
@@ -9,63 +9,55 @@ use crate::{
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, Tool)]
|
#[impl_message(Message, Tool)]
|
||||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum ToolMessage {
|
pub enum ToolMessage {
|
||||||
SelectPrimaryColor(Color),
|
|
||||||
SelectSecondaryColor(Color),
|
|
||||||
SwapColors,
|
|
||||||
ResetColors,
|
|
||||||
NoOp,
|
|
||||||
ActivateTool(ToolType),
|
ActivateTool(ToolType),
|
||||||
|
#[child]
|
||||||
|
Crop(CropMessage),
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
UpdateHints,
|
|
||||||
SetToolOptions(ToolType, ToolOptions),
|
|
||||||
#[child]
|
|
||||||
Fill(FillMessage),
|
|
||||||
#[child]
|
|
||||||
Rectangle(RectangleMessage),
|
|
||||||
#[child]
|
#[child]
|
||||||
Ellipse(EllipseMessage),
|
Ellipse(EllipseMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Select(SelectMessage),
|
Eyedropper(EyedropperMessage),
|
||||||
|
#[child]
|
||||||
|
Fill(FillMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Line(LineMessage),
|
Line(LineMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Crop(CropMessage),
|
|
||||||
#[child]
|
|
||||||
Eyedropper(EyedropperMessage),
|
|
||||||
#[child]
|
|
||||||
Navigate(NavigateMessage),
|
Navigate(NavigateMessage),
|
||||||
|
NoOp,
|
||||||
#[child]
|
#[child]
|
||||||
Path(PathMessage),
|
Path(PathMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Pen(PenMessage),
|
Pen(PenMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
Rectangle(RectangleMessage),
|
||||||
|
ResetColors,
|
||||||
|
#[child]
|
||||||
|
Select(SelectMessage),
|
||||||
|
SelectPrimaryColor(Color),
|
||||||
|
SelectSecondaryColor(Color),
|
||||||
|
SetToolOptions(ToolType, ToolOptions),
|
||||||
|
#[child]
|
||||||
Shape(ShapeMessage),
|
Shape(ShapeMessage),
|
||||||
|
SwapColors,
|
||||||
|
UpdateHints,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ToolMessageHandler {
|
pub struct ToolMessageHandler {
|
||||||
tool_state: ToolFsmState,
|
tool_state: ToolFsmState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)> for ToolMessageHandler {
|
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)> for ToolMessageHandler {
|
||||||
|
#[remain::check]
|
||||||
fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
fn process_action(&mut self, message: ToolMessage, data: (&DocumentMessageHandler, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||||
let (document, input) = data;
|
let (document, input) = data;
|
||||||
use ToolMessage::*;
|
use ToolMessage::*;
|
||||||
|
#[remain::sorted]
|
||||||
match message {
|
match message {
|
||||||
SelectPrimaryColor(color) => {
|
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
|
||||||
document_data.primary_color = color;
|
|
||||||
|
|
||||||
update_working_colors(&self.tool_state.document_tool_data, responses);
|
|
||||||
}
|
|
||||||
SelectSecondaryColor(color) => {
|
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
|
||||||
document_data.secondary_color = color;
|
|
||||||
|
|
||||||
update_working_colors(document_data, responses);
|
|
||||||
}
|
|
||||||
ActivateTool(new_tool) => {
|
ActivateTool(new_tool) => {
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
let document_data = &self.tool_state.document_tool_data;
|
let document_data = &self.tool_state.document_tool_data;
|
||||||
@@ -114,13 +106,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
responses.push_back(message.into());
|
responses.push_back(message.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SwapColors => {
|
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
|
||||||
|
|
||||||
std::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color);
|
|
||||||
|
|
||||||
update_working_colors(document_data, responses);
|
|
||||||
}
|
|
||||||
ResetColors => {
|
ResetColors => {
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
|
||||||
@@ -129,24 +114,44 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, &InputPreprocessor)>
|
|||||||
|
|
||||||
update_working_colors(document_data, responses);
|
update_working_colors(document_data, responses);
|
||||||
}
|
}
|
||||||
|
SelectPrimaryColor(color) => {
|
||||||
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
document_data.primary_color = color;
|
||||||
|
|
||||||
|
update_working_colors(&self.tool_state.document_tool_data, responses);
|
||||||
|
}
|
||||||
|
SelectSecondaryColor(color) => {
|
||||||
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
document_data.secondary_color = color;
|
||||||
|
|
||||||
|
update_working_colors(document_data, responses);
|
||||||
|
}
|
||||||
SetToolOptions(tool_type, tool_options) => {
|
SetToolOptions(tool_type, tool_options) => {
|
||||||
let document_data = &mut self.tool_state.document_tool_data;
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
|
||||||
document_data.tool_options.insert(tool_type, tool_options);
|
document_data.tool_options.insert(tool_type, tool_options);
|
||||||
}
|
}
|
||||||
message => {
|
SwapColors => {
|
||||||
let tool_type = message_to_tool_type(&message);
|
let document_data = &mut self.tool_state.document_tool_data;
|
||||||
|
|
||||||
|
std::mem::swap(&mut document_data.primary_color, &mut document_data.secondary_color);
|
||||||
|
|
||||||
|
update_working_colors(document_data, responses);
|
||||||
|
}
|
||||||
|
tool_message => {
|
||||||
|
let tool_type = message_to_tool_type(&tool_message);
|
||||||
let document_data = &self.tool_state.document_tool_data;
|
let document_data = &self.tool_state.document_tool_data;
|
||||||
let tool_data = &mut self.tool_state.tool_data;
|
let tool_data = &mut self.tool_state.tool_data;
|
||||||
|
|
||||||
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
|
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
|
||||||
if tool_type == tool_data.active_tool_type {
|
if tool_type == tool_data.active_tool_type {
|
||||||
tool.process_action(message, (document, document_data, input), responses);
|
tool.process_action(tool_message, (document, document_data, input), responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool, SetToolOptions);
|
let mut list = actions!(ToolMessageDiscriminant; ResetColors, SwapColors, ActivateTool, SetToolOptions);
|
||||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Crop;
|
pub struct Crop;
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Crop)]
|
#[impl_message(Message, ToolMessage, Crop)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum CropMessage {
|
pub enum CropMessage {
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ pub struct Ellipse {
|
|||||||
data: EllipseToolData,
|
data: EllipseToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Ellipse)]
|
#[impl_message(Message, ToolMessage, Ellipse)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum EllipseMessage {
|
pub enum EllipseMessage {
|
||||||
|
Abort,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
Resize { center: Key, lock_ratio: Key },
|
Resize { center: Key, lock_ratio: Key },
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Ellipse {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Ellipse {
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ pub struct Eyedropper {
|
|||||||
data: EyedropperToolData,
|
data: EyedropperToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Eyedropper)]
|
#[impl_message(Message, ToolMessage, Eyedropper)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum EyedropperMessage {
|
pub enum EyedropperMessage {
|
||||||
|
Abort,
|
||||||
LeftMouseDown,
|
LeftMouseDown,
|
||||||
RightMouseDown,
|
RightMouseDown,
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Eyedropper {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Eyedropper {
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ pub struct Fill {
|
|||||||
data: FillToolData,
|
data: FillToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Fill)]
|
#[impl_message(Message, ToolMessage, Fill)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum FillMessage {
|
pub enum FillMessage {
|
||||||
|
Abort,
|
||||||
LeftMouseDown,
|
LeftMouseDown,
|
||||||
RightMouseDown,
|
RightMouseDown,
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ pub struct Line {
|
|||||||
data: LineToolData,
|
data: LineToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Line)]
|
#[impl_message(Message, ToolMessage, Line)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum LineMessage {
|
pub enum LineMessage {
|
||||||
|
Abort,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
Redraw { center: Key, lock_angle: Key, snap_angle: Key },
|
Redraw { center: Key, lock_angle: Key, snap_angle: Key },
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Line {
|
||||||
|
|||||||
@@ -11,16 +11,17 @@ pub struct Navigate {
|
|||||||
data: NavigateToolData,
|
data: NavigateToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Navigate)]
|
#[impl_message(Message, ToolMessage, Navigate)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum NavigateMessage {
|
pub enum NavigateMessage {
|
||||||
|
Abort,
|
||||||
ClickZoom { zoom_in: bool },
|
ClickZoom { zoom_in: bool },
|
||||||
MouseMove { snap_angle: Key, snap_zoom: Key },
|
MouseMove { snap_angle: Key, snap_zoom: Key },
|
||||||
TranslateCanvasBegin,
|
|
||||||
RotateCanvasBegin,
|
RotateCanvasBegin,
|
||||||
ZoomCanvasBegin,
|
|
||||||
TransformCanvasEnd,
|
TransformCanvasEnd,
|
||||||
Abort,
|
TranslateCanvasBegin,
|
||||||
|
ZoomCanvasBegin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Navigate {
|
||||||
|
|||||||
@@ -27,16 +27,17 @@ pub struct Path {
|
|||||||
data: PathToolData,
|
data: PathToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Path)]
|
#[impl_message(Message, ToolMessage, Path)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum PathMessage {
|
pub enum PathMessage {
|
||||||
DragStart,
|
|
||||||
PointerMove,
|
|
||||||
DragStop,
|
|
||||||
|
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
DocumentIsDirty,
|
DocumentIsDirty,
|
||||||
|
|
||||||
|
DragStart,
|
||||||
|
DragStop,
|
||||||
|
PointerMove,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Path {
|
||||||
|
|||||||
@@ -14,15 +14,16 @@ pub struct Pen {
|
|||||||
data: PenToolData,
|
data: PenToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Pen)]
|
#[impl_message(Message, ToolMessage, Pen)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum PenMessage {
|
pub enum PenMessage {
|
||||||
Undo,
|
Abort,
|
||||||
|
Confirm,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
Confirm,
|
Undo,
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ pub struct Rectangle {
|
|||||||
data: RectangleToolData,
|
data: RectangleToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Rectangle)]
|
#[impl_message(Message, ToolMessage, Rectangle)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum RectangleMessage {
|
pub enum RectangleMessage {
|
||||||
|
Abort,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
Resize { center: Key, lock_ratio: Key },
|
Resize { center: Key, lock_ratio: Key },
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Rectangle {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Rectangle {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub struct Select {
|
|||||||
data: SelectToolData,
|
data: SelectToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[remain::sorted] // https://github.com/dtolnay/remain/issues/16
|
||||||
#[impl_message(Message, ToolMessage, Select)]
|
#[impl_message(Message, ToolMessage, Select)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum SelectMessage {
|
pub enum SelectMessage {
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ pub struct Shape {
|
|||||||
data: ShapeToolData,
|
data: ShapeToolData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[remain::sorted]
|
||||||
#[impl_message(Message, ToolMessage, Shape)]
|
#[impl_message(Message, ToolMessage, Shape)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Debug, Hash, Serialize, Deserialize)]
|
||||||
pub enum ShapeMessage {
|
pub enum ShapeMessage {
|
||||||
|
Abort,
|
||||||
DragStart,
|
DragStart,
|
||||||
DragStop,
|
DragStop,
|
||||||
Resize { center: Key, lock_ratio: Key },
|
Resize { center: Key, lock_ratio: Key },
|
||||||
Abort,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Shape {
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Shape {
|
||||||
|
|||||||
Reference in New Issue
Block a user