mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-18 07:48:02 +08:00
Implement anchor and handle point rendering with the Path Tool (#353)
* Implement Path Tool * Draw a red rectangle where the first point on the shape is * Correctly render anchors, handles, and connecting lines * Fix drain() which can panic * Refactor frontend messages to work as return values not callbacks * Reduce the number of unnecessary frontend updates * Fix stack overflow by using a loop * Group Document Render calls and put them at the end * Speed hacks for dirtification * Add performance * Bunch folder changed updates * Add triggers to redraw overlays to movement_handler * Polish the pixel-perfect rendering of vector manipulators * Restore scrollbars that were disabled * Cleanup * WIP Add shape outline rendering * Fix compiling * Add outlines to selected shapes * Fix outlines rendering over handles and anchors * Fix dirtification * Add a comment * Address code review feedback * Formatting * Small tweaks Co-authored-by: Oliver Davies <oliver@psyfer.io> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
@@ -5,7 +5,8 @@ use crate::{
|
||||
EditorError,
|
||||
};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::{document::Document as InternalDocument, DocumentError, LayerId};
|
||||
use graphene::{document::Document as InternalDocument, layers::LayerDataType, DocumentError, LayerId};
|
||||
use kurbo::PathSeg;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -41,6 +42,20 @@ pub enum AlignAggregate {
|
||||
Average,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum VectorManipulatorSegment {
|
||||
Line(DVec2, DVec2),
|
||||
Quad(DVec2, DVec2, DVec2),
|
||||
Cubic(DVec2, DVec2, DVec2, DVec2),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct VectorManipulatorShape {
|
||||
pub path: kurbo::BezPath,
|
||||
pub segments: Vec<VectorManipulatorSegment>,
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocumentMessageHandler {
|
||||
pub document: InternalDocument,
|
||||
@@ -65,7 +80,7 @@ impl Default for DocumentMessageHandler {
|
||||
}
|
||||
|
||||
#[impl_message(Message, DocumentsMessage, Document)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentMessage {
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
@@ -112,6 +127,7 @@ impl From<DocumentOperation> for DocumentMessage {
|
||||
Self::DispatchOperation(Box::new(operation))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DocumentOperation> for Message {
|
||||
fn from(operation: DocumentOperation) -> Message {
|
||||
DocumentMessage::DispatchOperation(Box::new(operation)).into()
|
||||
@@ -119,11 +135,6 @@ impl From<DocumentOperation> for Message {
|
||||
}
|
||||
|
||||
impl DocumentMessageHandler {
|
||||
fn filter_document_responses(&self, document_responses: &mut Vec<DocumentResponse>) -> bool {
|
||||
let len = document_responses.len();
|
||||
document_responses.retain(|response| !matches!(response, DocumentResponse::DocumentChanged));
|
||||
document_responses.len() != len
|
||||
}
|
||||
pub fn handle_folder_changed(&mut self, path: Vec<LayerId>) -> Option<Message> {
|
||||
let _ = self.document.render_root();
|
||||
self.layer_data(&path).expanded.then(|| {
|
||||
@@ -131,9 +142,11 @@ impl DocumentMessageHandler {
|
||||
FrontendMessage::ExpandFolder { path, children }.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn clear_selection(&mut self) {
|
||||
self.layer_data.values_mut().for_each(|layer_data| layer_data.selected = false);
|
||||
}
|
||||
|
||||
fn select_layer(&mut self, path: &[LayerId]) -> Option<Message> {
|
||||
if self.document.layer(path).ok()?.overlay {
|
||||
return None;
|
||||
@@ -143,13 +156,51 @@ impl DocumentMessageHandler {
|
||||
// TODO: Add deduplication
|
||||
(!path.is_empty()).then(|| FrontendMessage::UpdateLayer { path: path.to_vec(), data }.into())
|
||||
}
|
||||
|
||||
pub fn selected_layers_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
let paths = self.selected_layers().map(|vec| &vec[..]);
|
||||
self.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> {
|
||||
let shapes = self.selected_layers().filter_map(|path_to_shape| {
|
||||
let viewport_transform = self.document.generate_transform_relative_to_viewport(path_to_shape.as_slice()).ok()?;
|
||||
|
||||
let shape = match &self.document.layer(path_to_shape.as_slice()).ok()?.data {
|
||||
LayerDataType::Shape(shape) => Some(shape),
|
||||
LayerDataType::Folder(_) => None,
|
||||
}?;
|
||||
let path = shape.path.clone();
|
||||
|
||||
let segments = path
|
||||
.segments()
|
||||
.map(|segment| -> VectorManipulatorSegment {
|
||||
let place = |point: kurbo::Point| -> DVec2 { viewport_transform.transform_point2(DVec2::from((point.x, point.y))) };
|
||||
|
||||
match segment {
|
||||
PathSeg::Line(line) => VectorManipulatorSegment::Line(place(line.p0), place(line.p1)),
|
||||
PathSeg::Quad(quad) => VectorManipulatorSegment::Quad(place(quad.p0), place(quad.p1), place(quad.p2)),
|
||||
PathSeg::Cubic(cubic) => VectorManipulatorSegment::Cubic(place(cubic.p0), place(cubic.p1), place(cubic.p2), place(cubic.p3)),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<VectorManipulatorSegment>>();
|
||||
|
||||
Some(VectorManipulatorShape {
|
||||
path,
|
||||
segments,
|
||||
transform: viewport_transform,
|
||||
})
|
||||
});
|
||||
|
||||
// TODO: Consider refactoring this in a way that avoids needing to collect() so we can skip the heap allocations
|
||||
shapes.collect::<Vec<VectorManipulatorShape>>()
|
||||
}
|
||||
|
||||
pub fn layerdata(&self, path: &[LayerId]) -> &LayerData {
|
||||
self.layer_data.get(path).expect("Layerdata does not exist")
|
||||
}
|
||||
|
||||
pub fn layerdata_mut(&mut self, path: &[LayerId]) -> &mut LayerData {
|
||||
self.layer_data.entry(path.to_vec()).or_insert_with(|| LayerData::new(true))
|
||||
}
|
||||
@@ -200,6 +251,7 @@ impl DocumentMessageHandler {
|
||||
pub fn non_selected_layers_sorted(&self) -> Vec<Vec<LayerId>> {
|
||||
self.layers_sorted(Some(false))
|
||||
}
|
||||
|
||||
pub fn with_name(name: String) -> Self {
|
||||
Self {
|
||||
document: InternalDocument::default(),
|
||||
@@ -210,6 +262,7 @@ impl DocumentMessageHandler {
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_name_and_content(name: String, serialized_content: String) -> Result<Self, EditorError> {
|
||||
let mut document = Self::with_name(name);
|
||||
let internal_document = InternalDocument::with_content(&serialized_content);
|
||||
@@ -271,7 +324,6 @@ impl DocumentMessageHandler {
|
||||
}
|
||||
|
||||
pub fn layer_panel_entry(&mut self, path: Vec<LayerId>) -> Result<LayerPanelEntry, EditorError> {
|
||||
self.document.render_root();
|
||||
let data: LayerData = *layer_data(&mut self.layer_data, &path);
|
||||
let layer = self.document.layer(&path)?;
|
||||
let entry = layer_panel_entry(&data, self.document.multiply_transforms(&path).unwrap(), layer, path);
|
||||
@@ -372,22 +424,25 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
}
|
||||
ToggleLayerVisibility(path) => {
|
||||
responses.push_back(DocumentOperation::ToggleVisibility { path }.into());
|
||||
responses.push_back(DocumentOperation::ToggleLayerVisibility { path }.into());
|
||||
}
|
||||
ToggleLayerExpansion(path) => {
|
||||
self.layer_data(&path).expanded ^= true;
|
||||
responses.extend(self.handle_folder_changed(path));
|
||||
responses.push_back(FolderChanged(path).into());
|
||||
}
|
||||
SelectionChanged => {
|
||||
// TODO: Hoist this duplicated code into wider system
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
SelectionChanged => responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into()),
|
||||
DeleteSelectedLayers => {
|
||||
self.backup();
|
||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_front(ToolMessage::SelectedLayersChanged.into());
|
||||
for path in self.selected_layers().cloned() {
|
||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||
}
|
||||
}
|
||||
ClearOverlays => {
|
||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
for path in self.layer_data.keys().filter(|path| self.document.layer(path).unwrap().overlay).cloned() {
|
||||
responses.push_front(DocumentOperation::DeleteLayer { path }.into());
|
||||
}
|
||||
@@ -407,8 +462,8 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
responses.extend(self.select_layer(&path));
|
||||
}
|
||||
// TODO: Correctly update layer panel in clear_selection instead of here
|
||||
responses.extend(self.handle_folder_changed(Vec::new()));
|
||||
responses.push_front(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(FolderChanged(Vec::new()).into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
SelectAllLayers => {
|
||||
let all_layer_paths = self
|
||||
@@ -427,46 +482,41 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
Undo => {
|
||||
responses.push_back(SelectMessage::Abort.into());
|
||||
responses.push_back(DocumentHistoryBackward.into());
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.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(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(RenderDocument.into());
|
||||
responses.push_back(FolderChanged(vec![]).into());
|
||||
}
|
||||
FolderChanged(path) => responses.extend(self.handle_folder_changed(path)),
|
||||
DispatchOperation(op) => match self.document.handle_operation(&op) {
|
||||
Ok(Some(mut document_responses)) => {
|
||||
let canvas_dirty = self.filter_document_responses(&mut document_responses);
|
||||
Ok(Some(document_responses)) => {
|
||||
responses.extend(
|
||||
document_responses
|
||||
.into_iter()
|
||||
.map(|response| match response {
|
||||
DocumentResponse::FolderChanged { path } => self.handle_folder_changed(path),
|
||||
DocumentResponse::FolderChanged { path } => Some(FolderChanged(path).into()),
|
||||
DocumentResponse::DeletedLayer { path } => {
|
||||
self.layer_data.remove(&path);
|
||||
|
||||
Some(SelectMessage::UpdateSelectionBoundingBox.into())
|
||||
Some(ToolMessage::SelectedLayersChanged.into())
|
||||
}
|
||||
DocumentResponse::LayerChanged { path } => Some(
|
||||
DocumentResponse::LayerChanged { path } => (!self.document.layer(&path).unwrap().overlay).then(|| {
|
||||
FrontendMessage::UpdateLayer {
|
||||
path: path.clone(),
|
||||
data: self.layer_panel_entry(path).unwrap(),
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
.into()
|
||||
}),
|
||||
DocumentResponse::CreatedLayer { path } => (!self.document.layer(&path).unwrap().overlay).then(|| SetSelectedLayers(vec![path]).into()),
|
||||
DocumentResponse::DocumentChanged => unreachable!(),
|
||||
DocumentResponse::DocumentChanged => Some(RenderDocument.into()),
|
||||
})
|
||||
.flatten(),
|
||||
);
|
||||
if canvas_dirty {
|
||||
responses.push_back(RenderDocument.into());
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("DocumentError: {:?}", e),
|
||||
Ok(_) => (),
|
||||
@@ -478,6 +528,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let scale = 0.5 + ASYMPTOTIC_EFFECT + self.layerdata(&[]).scale * SCALE_EFFECT;
|
||||
let viewport_size = ipp.viewport_bounds.size();
|
||||
let viewport_mid = ipp.viewport_bounds.center();
|
||||
@@ -507,7 +558,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
};
|
||||
responses.push_back(operation.into());
|
||||
}
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
MoveSelectedLayersTo { path, insert_index } => {
|
||||
responses.push_back(DocumentsMessage::CopySelectedLayers.into());
|
||||
@@ -564,7 +615,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
}
|
||||
AlignSelectedLayers(axis, aggregate) => {
|
||||
@@ -598,12 +649,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
}
|
||||
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(DocumentMessageDiscriminant;
|
||||
Undo,
|
||||
|
||||
@@ -4,13 +4,14 @@ use graphene::layers::Layer;
|
||||
use graphene::{LayerId, Operation as DocumentOperation};
|
||||
use log::warn;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::DocumentMessageHandler;
|
||||
use crate::consts::DEFAULT_DOCUMENT_NAME;
|
||||
|
||||
#[impl_message(Message, Documents)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum DocumentsMessage {
|
||||
CopySelectedLayers,
|
||||
PasteLayers {
|
||||
|
||||
@@ -7,7 +7,7 @@ mod movement_handler;
|
||||
pub use document_file::LayerData;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis};
|
||||
pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMessageDiscriminant, DocumentMessageHandler, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
|
||||
#[doc(inline)]
|
||||
pub use document_message_handler::{DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
||||
#[doc(inline)]
|
||||
|
||||
@@ -11,10 +11,11 @@ use glam::DVec2;
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[impl_message(Message, DocumentMessage, Movement)]
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MovementMessage {
|
||||
MouseMove,
|
||||
TranslateCanvasBegin,
|
||||
@@ -91,6 +92,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
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);
|
||||
}
|
||||
if self.rotating {
|
||||
@@ -105,7 +107,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
|
||||
layerdata.rotation += rotation;
|
||||
layerdata.snap_rotate = snapping;
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
responses.push_back(
|
||||
FrontendMessage::SetCanvasRotation {
|
||||
new_radians: layerdata.snapped_angle(),
|
||||
@@ -121,6 +123,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
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);
|
||||
}
|
||||
self.mouse_pos = ipp.mouse.position;
|
||||
@@ -128,16 +131,19 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
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());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
IncreaseCanvasZoom => {
|
||||
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());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
DecreaseCanvasZoom => {
|
||||
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());
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
}
|
||||
WheelCanvasZoom => {
|
||||
@@ -158,6 +164,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
layerdata.scale = new;
|
||||
layerdata.translation += transformed_delta;
|
||||
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);
|
||||
}
|
||||
WheelCanvasTranslate { use_y_as_x } => {
|
||||
@@ -167,13 +174,14 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
} * 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);
|
||||
}
|
||||
SetCanvasRotation(new) => {
|
||||
layerdata.rotation = new;
|
||||
self.create_document_transform_from_layerdata(layerdata, &ipp.viewport_bounds, responses);
|
||||
responses.push_back(FrontendMessage::SetCanvasRotation { new_radians: new }.into());
|
||||
responses.push_back(SelectMessage::UpdateSelectionBoundingBox.into());
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some([pos1, pos2]) = document.visible_layers_bounding_box() {
|
||||
@@ -190,6 +198,7 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
layerdata.translation += center;
|
||||
layerdata.scale *= new_scale;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -197,12 +206,14 @@ impl MessageHandler<MovementMessage, (&mut LayerData, &Document, &InputPreproces
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user