Overhaul Path Tool (#498)

* First pass cleanup omw to handles

* Handles dragging with anchors, handles still not draggable and some bugs

* Dragging single side of handle works, need to create mirror case

* In progress addition of improved anchor / handle representation

* partially working

* Handle dragging working for non-end points, normal anchor drag bugged

* Fixed corner cases, fixed anchors without handles bug

* Add snapping

* Change path tool selection by clicking on shape

* Fixed path close point being draggable

* Variable length handle, firstpass of alt to stop mirroring

* Alt improved, not done. Only update structures when needed. Added snapping for selected shapes

* Can now undo path edits

* Do not maintain angle between non-mirrored handles

* Replaced segment based overlay setup with anchor based setup

* Cleanup, handle angle comparison bug remains. Investigating.

* Added OverlayPooler. May closely associate overlays to VectorManipulatorAnchors instead.

* Moved anchor / segment creation logic out of document_message_handler

* Overlays are now managed by VectorManipulatorShapes

* Fixed inconsistent handle mirroring.

* Clearly shows which point you have selected

* Removed OverlayPooler system

* Added more comments

* Removed all clones of the vector structures. A little uglier but better.

* Resolved Text path initialization bug with a workaround.

* Cleaned up comments

* More comment cleanup

* Fixed issue with quad handle dragging unwanted behavior, renamed VectorShapeManipulator

* In progress refactor to allow multi-selection

* In progress dragging multiple points, selection works, transform still has issues

* Added Multiselect, major refactor

* Commented out progress for selection change, bug with hop back on multiple shapes

* Removed debug og

* Resolved issue with merge

* Minor cleanup, added a few comments

* Review changes

* Resolved unclear comment

* Fixed snap back for now

* Add todo comment for future snap back fix

* Working situations where curve paths do not close. Thanks for points it out  @pkupper

* Tweaked selection size

* Fix curve start point dragability, renames, cleanup

* Separated into multiple files, applied @TrueDoctor review feedback

* Resolved tests failing due to doc generation

* Re-added closed, added concept of distance mirroring

* Added shift distance mirroring, removed debounce from anchor

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Thank you for the reviews @TrueDoctor and @pkupper
This commit is contained in:
Oliver Davies
2022-02-09 12:49:14 -08:00
committed by Keavon Chambers
parent bd844aaf94
commit 108b8be595
17 changed files with 1407 additions and 526 deletions

View File

@@ -1,6 +1,6 @@
use super::clipboards::Clipboard;
use super::layer_panel::{layer_panel_entry, LayerDataTypeDiscriminant, LayerMetadata, LayerPanelEntry, RawBuffer};
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis, VectorManipulatorSegment, VectorManipulatorShape};
use super::utility_types::{AlignAggregate, AlignAxis, DocumentSave, FlipAxis};
use super::vectorize_layer_metadata;
use super::{ArtboardMessageHandler, MovementMessageHandler, OverlaysMessageHandler, TransformLayerMessageHandler};
use crate::consts::{
@@ -12,6 +12,7 @@ use crate::layout::widgets::{
WidgetCallback, WidgetHolder, WidgetLayout,
};
use crate::message_prelude::*;
use crate::viewport_tools::vector_editor::vector_shape::VectorShape;
use crate::EditorError;
use graphene::document::Document as GrapheneDocument;
@@ -21,7 +22,6 @@ use graphene::layers::style::ViewMode;
use graphene::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
use glam::{DAffine2, DVec2};
use kurbo::PathSeg;
use log::warn;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
@@ -138,49 +138,26 @@ impl DocumentMessageHandler {
self.graphene_document.combined_viewport_bounding_box(paths)
}
// TODO: Consider moving this to some kind of overlays manager in the future
pub fn selected_visible_layers_vector_points(&self) -> Vec<VectorManipulatorShape> {
/// Create a new vector shape representation with the underlying kurbo data, VectorManipulatorShape
pub fn selected_visible_layers_vector_shapes(&self, responses: &mut VecDeque<Message>) -> Vec<VectorShape> {
let shapes = self.selected_layers().filter_map(|path_to_shape| {
let viewport_transform = self.graphene_document.generate_transform_relative_to_viewport(path_to_shape).ok()?;
let layer = self.graphene_document.layer(path_to_shape);
// Filter out the non-visible layers from the `filter_map`
match &layer {
Ok(layer) if layer.visible => {}
_ => return None,
};
let (path, closed) = match &layer.ok()?.data {
// TODO: This ClosePath check does not handle all cases, fix this soon
LayerDataType::Shape(shape) => Some((shape.path.clone(), shape.path.elements().last() == Some(&kurbo::PathEl::ClosePath))),
LayerDataType::Text(text) => Some((text.to_bez_path_nonmut(), true)),
// TODO: Create VectorManipulatorShape when creating a kurbo shape as a stopgap, rather than on each new selection
match &layer.ok()?.data {
LayerDataType::Shape(shape) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &shape.path, shape.closed, responses)),
LayerDataType::Text(text) => Some(VectorShape::new(path_to_shape.to_vec(), viewport_transform, &text.to_bez_path_nonmut(), true, responses)),
_ => None,
}?;
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 {
layer_path: path_to_shape.to_vec(),
path,
segments,
transform: viewport_transform,
closed,
})
}
});
// TODO: Consider refactoring this in a way that avoids needing to collect() so we can skip the heap allocations
shapes.collect::<Vec<VectorManipulatorShape>>()
shapes.collect::<Vec<VectorShape>>()
}
pub fn selected_layers(&self) -> impl Iterator<Item = &[LayerId]> {
@@ -693,7 +670,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}
// TODO: Correctly update layer panel in clear_selection instead of here
responses.push_back(FolderChanged { affected_folder_path: vec![] }.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::SelectionChanged.into());
}
AlignSelectedLayers { axis, aggregate } => {
self.backup(responses);
@@ -756,7 +733,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
responses.push_front(DocumentOperation::DeleteLayer { path: path.to_vec() }.into());
}
responses.push_front(ToolMessage::DocumentIsDirty.into());
responses.push_front(DocumentMessage::SelectionChanged.into());
}
DeselectAllLayers => {
responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into());
@@ -1042,6 +1019,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}
SelectionChanged => {
// TODO: Hoist this duplicated code into wider system
responses.push_back(ToolMessage::SelectionChanged.into());
responses.push_back(ToolMessage::DocumentIsDirty.into());
}
SelectLayer { layer_path, ctrl, shift } => {
@@ -1068,7 +1046,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessorMessageHandler> for Docum
}
.into(),
);
responses.push_back(ToolMessage::DocumentIsDirty.into());
responses.push_back(DocumentMessage::SelectionChanged.into());
} else {
paths.push(layer_path.clone());
}