mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 18:38:05 +08:00
New overlay system that reimplements how overlays are drawn (#418)
* New overlay system that reimplements how overlays are drawn * Fix overlay message declaration * Fix small mistake * WIP (broken) changes to plumb the overlay document * Fix confusion over messaging system architecture * Removed log * Overlay system working * (broken) WIP overlay association * Finish the overlay system (except test failure) * Change back IDs in test * Fixed test, but stilled fails due to revealed real problem with layer reordering selection * Disable broken test that has a bug in issue #444 Co-authored-by: Dennis <dennis@kobert.dev> Co-authored-by: otdavies <oliver@psyfer.io>
This commit is contained in:
@@ -4,6 +4,7 @@ use std::collections::VecDeque;
|
||||
use super::document_message_handler::CopyBufferEntry;
|
||||
pub use super::layer_panel::*;
|
||||
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
||||
use super::overlay_message_handler::OverlayMessageHandler;
|
||||
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
|
||||
use super::vectorize_layerdata;
|
||||
|
||||
@@ -14,16 +15,16 @@ use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use crate::EditorError;
|
||||
|
||||
use graphene::layers::{style::ViewMode, BlendMode, LayerDataType};
|
||||
use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId};
|
||||
use graphene::{DocumentResponse, Operation as DocumentOperation};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::layers::Folder;
|
||||
use kurbo::PathSeg;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use graphene::layers::{style::ViewMode, BlendMode, LayerDataType};
|
||||
use graphene::{document::Document as GrapheneDocument, DocumentError, LayerId};
|
||||
use graphene::{DocumentResponse, Operation as DocumentOperation};
|
||||
|
||||
type DocumentSave = (GrapheneDocument, HashMap<Vec<LayerId>, LayerData>);
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Hash)]
|
||||
@@ -75,6 +76,8 @@ pub struct DocumentMessageHandler {
|
||||
#[serde(skip)]
|
||||
movement_handler: MovementMessageHandler,
|
||||
#[serde(skip)]
|
||||
overlay_message_handler: OverlayMessageHandler,
|
||||
#[serde(skip)]
|
||||
transform_layer_handler: TransformLayerMessageHandler,
|
||||
pub snapping_enabled: bool,
|
||||
pub view_mode: ViewMode,
|
||||
@@ -91,6 +94,7 @@ impl Default for DocumentMessageHandler {
|
||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||
layer_range_selection_reference: Vec::new(),
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
overlay_message_handler: OverlayMessageHandler::default(),
|
||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||
snapping_enabled: true,
|
||||
view_mode: ViewMode::default(),
|
||||
@@ -106,6 +110,8 @@ pub enum DocumentMessage {
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
#[child]
|
||||
Overlay(OverlayMessage),
|
||||
UpdateLayerData {
|
||||
path: Vec<LayerId>,
|
||||
layer_data_entry: LayerData,
|
||||
@@ -113,6 +119,7 @@ pub enum DocumentMessage {
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
SelectAllLayers,
|
||||
DebugPrintDocument,
|
||||
SelectLayer(Vec<LayerId>, bool, bool),
|
||||
SelectionChanged,
|
||||
DeselectAllLayers,
|
||||
@@ -145,7 +152,6 @@ pub enum DocumentMessage {
|
||||
Redo,
|
||||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
ClearOverlays,
|
||||
NudgeSelectedLayers(f64, f64),
|
||||
AlignSelectedLayers(AlignAxis, AlignAggregate),
|
||||
MoveSelectedLayersTo {
|
||||
@@ -210,25 +216,31 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
|
||||
if self.graphene_document.layer(path).ok()?.overlay {
|
||||
return None;
|
||||
}
|
||||
println!("Select_layer fail: {:?}", self.all_layers_sorted());
|
||||
|
||||
self.layer_data_mut(path).selected = true;
|
||||
let data = self.layer_panel_entry(path.to_vec()).ok()?;
|
||||
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { data }.into())
|
||||
}
|
||||
|
||||
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
let paths = self.selected_layers();
|
||||
pub fn selected_visible_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
let paths = self.selected_visible_layers();
|
||||
self.graphene_document.combined_viewport_bounding_box(paths)
|
||||
}
|
||||
|
||||
// TODO: Consider moving this to some kind of overlay manager in the future
|
||||
pub fn selected_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
|
||||
pub fn selected_visible_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
|
||||
let shapes = self.selected_layers().filter_map(|path_to_shape| {
|
||||
let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?;
|
||||
let layer = self.graphene_document.layer(path_to_shape);
|
||||
|
||||
let shape = match &self.graphene_document.layer(path_to_shape).ok()?.data {
|
||||
// Filter out the non-visible layers from the filter_map
|
||||
match &layer {
|
||||
Ok(layer) if layer.visible => {}
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let shape = match &layer.ok()?.data {
|
||||
LayerDataType::Shape(shape) => Some(shape),
|
||||
LayerDataType::Folder(_) => None,
|
||||
}?;
|
||||
@@ -266,6 +278,13 @@ impl DocumentMessageHandler {
|
||||
self.layer_data.iter().filter_map(|(path, data)| data.selected.then(|| path.as_slice()))
|
||||
}
|
||||
|
||||
pub fn selected_visible_layers(&self) -> impl Iterator<Item = &[LayerId]> {
|
||||
self.selected_layers().filter(|path| match self.graphene_document.layer(path) {
|
||||
Ok(layer) => layer.visible,
|
||||
Err(_) => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_structure(&self, folder: &Folder, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
|
||||
let mut space = 0;
|
||||
for (id, layer) in folder.layer_ids.iter().zip(folder.layers()) {
|
||||
@@ -319,6 +338,11 @@ impl DocumentMessageHandler {
|
||||
structure
|
||||
}
|
||||
|
||||
/// Returns an unsorted list of all layer paths including folders at all levels, except the document's top-level root folder itself
|
||||
pub fn all_layers(&self) -> Vec<Vec<LayerId>> {
|
||||
self.layer_data.keys().filter(|path| !path.is_empty()).cloned().collect()
|
||||
}
|
||||
|
||||
/// Returns the paths to all layers in order, optionally including only selected or non-selected layers.
|
||||
fn layers_sorted(&self, selected: Option<bool>) -> Vec<Vec<LayerId>> {
|
||||
// Compute the indices for each layer to be able to sort them
|
||||
@@ -378,12 +402,7 @@ impl DocumentMessageHandler {
|
||||
|
||||
pub fn backup(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.document_redo_history.clear();
|
||||
let new_layer_data = self
|
||||
.layer_data
|
||||
.iter()
|
||||
.filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
|
||||
.collect();
|
||||
self.document_undo_history.push((self.graphene_document.clone_without_overlays(), new_layer_data));
|
||||
self.document_undo_history.push((self.graphene_document.clone(), self.layer_data.clone()));
|
||||
|
||||
// Push the UpdateOpenDocumentsList message to the bus in order to update the save status of the open documents
|
||||
responses.push_back(DocumentsMessage::UpdateOpenDocumentsList.into());
|
||||
@@ -418,11 +437,7 @@ impl DocumentMessageHandler {
|
||||
Some((document, layer_data)) => {
|
||||
let document = std::mem::replace(&mut self.graphene_document, document);
|
||||
let layer_data = std::mem::replace(&mut self.layer_data, layer_data);
|
||||
let new_layer_data = layer_data
|
||||
.iter()
|
||||
.filter_map(|(key, value)| (!self.graphene_document.layer(key).unwrap().overlay).then(|| (key.clone(), *value)))
|
||||
.collect();
|
||||
self.document_undo_history.push((document.clone_without_overlays(), new_layer_data));
|
||||
self.document_undo_history.push((document.clone(), layer_data.clone()));
|
||||
Ok(())
|
||||
}
|
||||
None => Err(EditorError::NoTransactionInProgress),
|
||||
@@ -474,10 +489,7 @@ impl DocumentMessageHandler {
|
||||
.ok()?;
|
||||
let layer = self.graphene_document.layer(path).ok()?;
|
||||
|
||||
match layer.overlay {
|
||||
true => None,
|
||||
false => Some(layer_panel_entry(layer_data, transform, layer, path.to_vec())),
|
||||
}
|
||||
Some(layer_panel_entry(layer_data, transform, layer, path.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,6 +514,11 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
|
||||
}
|
||||
CommitTransaction => (),
|
||||
Overlay(message) => {
|
||||
self.overlay_message_handler
|
||||
.process_action(message, (Self::layer_data_mut_no_borrow_self(&mut self.layer_data, &[]), &self.graphene_document, ipp), responses);
|
||||
// responses.push_back(OverlayMessage::RenderOverlays.into());
|
||||
}
|
||||
ExportDocument => {
|
||||
let bbox = self.graphene_document.visible_layers_bounding_box().unwrap_or([DVec2::ZERO, ipp.viewport_bounds.size()]);
|
||||
let size = bbox[1] - bbox[0];
|
||||
@@ -588,6 +605,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
ToggleLayerVisibility(path) => {
|
||||
responses.push_back(DocumentOperation::ToggleLayerVisibility { path }.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ToggleLayerExpansion(path) => {
|
||||
self.layer_data_mut(&path).expanded ^= true;
|
||||
@@ -601,21 +619,15 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
SelectionChanged => {
|
||||
// TODO: Hoist this duplicated code into wider system
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
DeleteSelectedLayers => {
|
||||
self.backup(responses);
|
||||
responses.push_front(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_front(ToolMessage::DocumentIsDirty.into());
|
||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||
}
|
||||
}
|
||||
ClearOverlays => {
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
for path in self.layer_data.keys().filter(|path| self.graphene_document.layer(path).unwrap().overlay).cloned() {
|
||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||
}
|
||||
}
|
||||
SetViewMode(mode) => {
|
||||
self.view_mode = mode;
|
||||
responses.push_front(DocumentMessage::DirtyRenderDocument.into());
|
||||
@@ -680,15 +692,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
// TODO: Correctly update layer panel in clear_selection instead of here
|
||||
responses.push_back(FolderChanged(Vec::new()).into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
DebugPrintDocument => {
|
||||
log::debug!("{:#?}\n{:#?}", self.graphene_document, self.layer_data);
|
||||
}
|
||||
SelectAllLayers => {
|
||||
let all_layer_paths = self
|
||||
.layer_data
|
||||
.keys()
|
||||
.filter(|path| !path.is_empty() && !self.graphene_document.layer(path).map(|layer| layer.overlay).unwrap_or(false))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let all_layer_paths = self.all_layers();
|
||||
responses.push_front(SetSelectedLayers(all_layer_paths).into());
|
||||
}
|
||||
DeselectAllLayers => {
|
||||
@@ -700,14 +710,14 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
Undo => {
|
||||
responses.push_back(SelectMessage::Abort.into());
|
||||
responses.push_back(DocumentHistoryBackward.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(RenderDocument.into());
|
||||
responses.push_back(FolderChanged(vec![]).into());
|
||||
}
|
||||
Redo => {
|
||||
responses.push_back(SelectMessage::Abort.into());
|
||||
responses.push_back(DocumentHistoryForward.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(RenderDocument.into());
|
||||
responses.push_back(FolderChanged(vec![]).into());
|
||||
}
|
||||
@@ -720,41 +730,37 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
responses.push_back(FrontendMessage::DisplayFolderTreeStructure { data_buffer }.into())
|
||||
}
|
||||
LayerChanged(path) => {
|
||||
responses.extend(self.layer_panel_entry(path.clone()).ok().and_then(|entry| {
|
||||
let overlay = self.graphene_document.layer(&path).unwrap().overlay;
|
||||
(!overlay).then(|| FrontendMessage::UpdateLayer { data: entry }.into())
|
||||
}));
|
||||
if let Ok(layer_entry) = self.layer_panel_entry(path) {
|
||||
responses.push_back(FrontendMessage::UpdateLayer { data: layer_entry }.into());
|
||||
}
|
||||
}
|
||||
DispatchOperation(op) => match self.graphene_document.handle_operation(&op) {
|
||||
Ok(Some(document_responses)) => {
|
||||
for response in document_responses {
|
||||
match response {
|
||||
DocumentResponse::FolderChanged { path } => responses.push_back(FolderChanged(path).into()),
|
||||
match &response {
|
||||
DocumentResponse::FolderChanged { path } => responses.push_back(FolderChanged(path.clone()).into()),
|
||||
DocumentResponse::DeletedLayer { path } => {
|
||||
self.layer_data.remove(&path);
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into())
|
||||
self.layer_data.remove(path);
|
||||
}
|
||||
DocumentResponse::LayerChanged { path } => responses.push_back(LayerChanged(path).into()),
|
||||
DocumentResponse::LayerChanged { path } => responses.push_back(LayerChanged(path.clone()).into()),
|
||||
DocumentResponse::CreatedLayer { path } => {
|
||||
self.layer_data.insert(path.clone(), LayerData::new(false));
|
||||
responses.push_back(LayerChanged(path.clone()).into());
|
||||
if !self.graphene_document.layer(&path).unwrap().overlay {
|
||||
self.layer_range_selection_reference = path.clone();
|
||||
responses.push_back(SetSelectedLayers(vec![path]).into());
|
||||
}
|
||||
self.layer_range_selection_reference = path.clone();
|
||||
responses.push_back(SetSelectedLayers(vec![path.clone()]).into());
|
||||
}
|
||||
DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()),
|
||||
};
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
// log::debug!("LayerPanel: {:?}", self.layer_data.keys());
|
||||
}
|
||||
Err(e) => log::error!("DocumentError: {:?}", e),
|
||||
Ok(_) => (),
|
||||
},
|
||||
RenderDocument => {
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateCanvas {
|
||||
document: self.graphene_document.render_root(self.view_mode),
|
||||
FrontendMessage::UpdateArtwork {
|
||||
svg: self.graphene_document.render_root(self.view_mode),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -815,7 +821,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
};
|
||||
responses.push_back(operation.into());
|
||||
}
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
MoveSelectedLayersTo { path, insert_index } => {
|
||||
responses.push_back(DocumentsMessage::Copy(Clipboard::System).into());
|
||||
@@ -870,7 +876,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
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().map(|x| x)) {
|
||||
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() {
|
||||
@@ -883,7 +889,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
AlignSelectedLayers(axis, aggregate) => {
|
||||
@@ -898,7 +904,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
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().map(|x| x)) {
|
||||
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],
|
||||
@@ -920,7 +926,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
||||
@@ -976,6 +982,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
ExportDocument,
|
||||
SaveDocument,
|
||||
SetSnapping,
|
||||
DebugPrintDocument,
|
||||
MoveLayerInTree,
|
||||
);
|
||||
|
||||
|
||||
@@ -135,10 +135,10 @@ impl DocumentsMessageHandler {
|
||||
.document_ids
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
self.documents.get(&id).map(|doc| FrontendDocumentDetails {
|
||||
is_saved: doc.is_saved(),
|
||||
self.documents.get(id).map(|document| FrontendDocumentDetails {
|
||||
is_saved: document.is_saved(),
|
||||
id: *id,
|
||||
name: doc.name.clone(),
|
||||
name: document.name.clone(),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -36,7 +36,7 @@ impl LayerData {
|
||||
}
|
||||
|
||||
pub fn layer_panel_entry(layer_data: &LayerData, transform: DAffine2, layer: &Layer, path: Vec<LayerId>) -> LayerPanelEntry {
|
||||
let layer_type: LayerType = (&layer.data).into();
|
||||
let layer_type: LayerDataTypeDiscriminant = (&layer.data).into();
|
||||
let name = layer.name.clone().unwrap_or_else(|| format!("Unnamed {}", layer_type));
|
||||
let arr = layer.data.bounding_box(transform).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let arr = arr.iter().map(|x| (*x).into()).collect::<Vec<(f64, f64)>>();
|
||||
@@ -103,35 +103,35 @@ pub struct LayerPanelEntry {
|
||||
pub visible: bool,
|
||||
pub blend_mode: BlendMode,
|
||||
pub opacity: f64,
|
||||
pub layer_type: LayerType,
|
||||
pub layer_type: LayerDataTypeDiscriminant,
|
||||
pub layer_data: LayerData,
|
||||
pub path: Vec<LayerId>,
|
||||
pub thumbnail: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||
pub enum LayerType {
|
||||
pub enum LayerDataTypeDiscriminant {
|
||||
Folder,
|
||||
Shape,
|
||||
}
|
||||
|
||||
impl fmt::Display for LayerType {
|
||||
impl fmt::Display for LayerDataTypeDiscriminant {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
let name = match self {
|
||||
LayerType::Folder => "Folder",
|
||||
LayerType::Shape => "Shape",
|
||||
LayerDataTypeDiscriminant::Folder => "Folder",
|
||||
LayerDataTypeDiscriminant::Shape => "Shape",
|
||||
};
|
||||
|
||||
formatter.write_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&LayerDataType> for LayerType {
|
||||
impl From<&LayerDataType> for LayerDataTypeDiscriminant {
|
||||
fn from(data: &LayerDataType) -> Self {
|
||||
use LayerDataType::*;
|
||||
match data {
|
||||
Folder(_) => LayerType::Folder,
|
||||
Shape(_) => LayerType::Shape,
|
||||
Folder(_) => LayerDataTypeDiscriminant::Folder,
|
||||
Shape(_) => LayerDataTypeDiscriminant::Shape,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ mod document_file;
|
||||
mod document_message_handler;
|
||||
pub mod layer_panel;
|
||||
mod movement_handler;
|
||||
mod overlay_message_handler;
|
||||
mod transform_layer_handler;
|
||||
mod vectorize_layerdata;
|
||||
|
||||
@@ -15,4 +16,6 @@ pub use document_message_handler::{Clipboard, DocumentsMessage, DocumentsMessage
|
||||
#[doc(inline)]
|
||||
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use overlay_message_handler::{OverlayMessage, OverlayMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
|
||||
@@ -69,7 +69,7 @@ impl MovementMessageHandler {
|
||||
|
||||
impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreprocessor)> for MovementMessageHandler {
|
||||
fn process_action(&mut self, message: MovementMessage, data: (&mut LayerData, &Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
let (layerdata, document, ipp) = data;
|
||||
let (layer_data, document, ipp) = data;
|
||||
use MovementMessage::*;
|
||||
match message {
|
||||
TranslateCanvasBegin => {
|
||||
@@ -89,7 +89,8 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
}
|
||||
TransformCanvasEnd => {
|
||||
layerdata.rotation = self.snapped_angle(layerdata);
|
||||
layer_data.rotation = self.snapped_angle(layer_data);
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.snap_rotate = false;
|
||||
self.translating = false;
|
||||
self.rotating = false;
|
||||
@@ -100,9 +101,9 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
let delta = ipp.mouse.position - self.mouse_pos;
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
|
||||
layerdata.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
layer_data.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
if self.rotating {
|
||||
let half_viewport = ipp.viewport_bounds.size() / 2.;
|
||||
@@ -114,51 +115,51 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
|
||||
let snapping = self.snapping;
|
||||
|
||||
layerdata.rotation += rotation;
|
||||
layer_data.rotation += rotation;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.snap_rotate = snapping;
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(
|
||||
FrontendMessage::SetCanvasRotation {
|
||||
new_radians: self.snapped_angle(layerdata),
|
||||
new_radians: self.snapped_angle(layer_data),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
if self.zooming {
|
||||
let difference = self.mouse_pos.y as f64 - ipp.mouse.position.y as f64;
|
||||
let amount = 1. + difference * VIEWPORT_ZOOM_MOUSE_RATE;
|
||||
|
||||
let new = (layerdata.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
layerdata.scale = new;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
let new = (layer_data.scale * amount).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
layer_data.scale = new;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
}
|
||||
SetCanvasZoom(new) => {
|
||||
layerdata.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
layer_data.scale = new.clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
IncreaseCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > layerdata.scale).unwrap_or(&layerdata.scale);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
layer_data.scale = *VIEWPORT_ZOOM_LEVELS.iter().find(|scale| **scale > layer_data.scale).unwrap_or(&layer_data.scale);
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
DecreaseCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
layerdata.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < layerdata.scale).unwrap_or(&layerdata.scale);
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
layer_data.scale = *VIEWPORT_ZOOM_LEVELS.iter().rev().find(|scale| **scale < layer_data.scale).unwrap_or(&layer_data.scale);
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
WheelCanvasZoom => {
|
||||
// TODO: Eliminate redundant code by making this call SetCanvasZoom
|
||||
@@ -175,13 +176,13 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
|
||||
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
let new = (layerdata.scale * zoom_factor).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
layerdata.scale = new;
|
||||
layerdata.translation += transformed_delta;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
let new = (layer_data.scale * zoom_factor).clamp(VIEWPORT_ZOOM_SCALE_MIN, VIEWPORT_ZOOM_SCALE_MAX);
|
||||
layer_data.scale = new;
|
||||
layer_data.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
WheelCanvasTranslate { use_y_as_x } => {
|
||||
let delta = match use_y_as_x {
|
||||
@@ -189,15 +190,15 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
true => (-ipp.mouse.scroll_delta.y as f64, 0.).into(),
|
||||
} * VIEWPORT_SCROLL_RATE;
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
layerdata.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
layer_data.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
SetCanvasRotation(new) => {
|
||||
layerdata.rotation = new;
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
layer_data.rotation = new;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
||||
@@ -211,27 +212,27 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
let size = 1. / size;
|
||||
let new_scale = size.min_element();
|
||||
|
||||
layerdata.translation += center;
|
||||
layerdata.scale *= new_scale;
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layerdata.scale }.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
layer_data.translation += center;
|
||||
layer_data.scale *= new_scale;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
responses.push_back(FrontendMessage::SetCanvasZoom { new_zoom: layer_data.scale }.into());
|
||||
responses.push_back(DocumentMessage::DirtyRenderDocumentInOutlineView.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
}
|
||||
TranslateCanvas(delta) => {
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta);
|
||||
|
||||
layerdata.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
layer_data.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
TranslateCanvasByViewportFraction(delta) => {
|
||||
let transformed_delta = document.root.transform.inverse().transform_vector2(delta * ipp.viewport_bounds.size());
|
||||
|
||||
layerdata.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
layer_data.translation += transformed_delta;
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
self.create_document_transform_from_layerdata(layer_data, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
58
editor/src/document/overlay_message_handler.rs
Normal file
58
editor/src/document/overlay_message_handler.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
pub use crate::document::layer_panel::*;
|
||||
use crate::document::{DocumentMessage, LayerData};
|
||||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use graphene::document::Document as GrapheneDocument;
|
||||
use graphene::layers::style::ViewMode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, Overlay)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum OverlayMessage {
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
ClearAllOverlays,
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for OverlayMessage {
|
||||
fn from(operation: DocumentOperation) -> OverlayMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct OverlayMessageHandler {
|
||||
pub overlays_graphene_document: GrapheneDocument,
|
||||
overlay_path_mapping: HashMap<Vec<LayerId>, Vec<LayerId>>,
|
||||
}
|
||||
|
||||
impl MessageHandler<OverlayMessage, (&mut LayerData, &Document, &InputPreprocessor)> for OverlayMessageHandler {
|
||||
fn process_action(&mut self, message: OverlayMessage, data: (&mut LayerData, &Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
let (layerdata, document, ipp) = data;
|
||||
use OverlayMessage::*;
|
||||
match message {
|
||||
DispatchOperation(operation) => match self.overlays_graphene_document.handle_operation(&operation) {
|
||||
Ok(_) => (),
|
||||
Err(e) => log::error!("OverlayError: {:?}", e),
|
||||
},
|
||||
ClearAllOverlays => todo!(),
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateOverlays {
|
||||
svg: self.overlays_graphene_document.render_root(ViewMode::Normal),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(OverlayMessageDiscriminant;
|
||||
ClearAllOverlays
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ impl<'a> Selected<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
self.responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData
|
||||
|
||||
self.operation = Operation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let Operation::Rotating(_) = self.operation {
|
||||
@@ -434,7 +434,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData
|
||||
|
||||
self.operation = Operation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let Operation::Scaling(_) = self.operation {
|
||||
@@ -446,7 +446,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData
|
||||
self.operation = Operation::Scaling(Default::default());
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
CancelOperation => {
|
||||
selected.revert_operation();
|
||||
@@ -456,7 +456,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
ApplyOperation => {
|
||||
self.original_transforms.clear();
|
||||
@@ -464,7 +464,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(ToolMessage::DocumentIsDirty.into());
|
||||
}
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
Reference in New Issue
Block a user