mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Migrate vector data and tools to use nodes (#1065)
* Add rendering to vector nodes * Add line, shape, rectange and freehand tool * Fix transforms, strokes and fills * Migrate spline tool * Remove blank lines * Fix test * Fix fill in properties * Select layers when filling * Properties panel transform around pivot * Fix select tool outlines * Select tool modifies node graph pivot * Add the pivot assist to the properties * Improve setting non existant fill UX * Cleanup hash function * Path and pen tools * Bug fixes * Disable boolean ops * Fix default handle smoothing on ellipses * Fix test and warnings --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
parent
639a24d8ad
commit
959e790cdf
@@ -8,8 +8,6 @@ use crate::layers::style::RenderData;
|
||||
use crate::layers::text_layer::{Font, TextLayer};
|
||||
use crate::{DocumentError, DocumentResponse, Operation};
|
||||
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cell::RefCell;
|
||||
@@ -177,43 +175,6 @@ impl Document {
|
||||
Ok(shapes)
|
||||
}
|
||||
|
||||
/// Return a copy of all [Subpath]s currently in the document.
|
||||
pub fn all_subpaths(&self) -> Vec<Subpath> {
|
||||
self.root.iter().flat_map(|layer| layer.as_subpath_copy()).collect::<Vec<Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns references to all [Subpath]s currently in the document.
|
||||
pub fn all_subpaths_ref(&self) -> Vec<&Subpath> {
|
||||
self.root.iter().flat_map(|layer| layer.as_subpath()).collect::<Vec<&Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns a reference to the requested [Subpath] by providing a path to its owner layer.
|
||||
pub fn subpath_ref<'a>(&'a self, path: &[LayerId]) -> Option<&'a Subpath> {
|
||||
self.layer(path).ok()?.as_subpath()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference of the requested [Subpath] by providing a path to its owner layer.
|
||||
pub fn subpath_mut<'a>(&'a mut self, path: &'a [LayerId]) -> Option<&'a mut Subpath> {
|
||||
self.layer_mut(path).ok()?.as_subpath_mut()
|
||||
}
|
||||
|
||||
/// Set a [Subpath] at the specified path.
|
||||
pub fn set_subpath(&mut self, path: &[LayerId], shape: Subpath) {
|
||||
let layer = self.layer_mut(path);
|
||||
if let Ok(layer) = layer {
|
||||
if let LayerDataType::Shape(shape_layer) = &mut layer.data {
|
||||
shape_layer.shape = shape;
|
||||
// Is this needed?
|
||||
layer.cache_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set [Subpath]s for multiple paths at once.
|
||||
pub fn set_subpaths<'a>(&'a mut self, paths: impl Iterator<Item = &'a [LayerId]>, shapes: Vec<Subpath>) {
|
||||
paths.zip(shapes).for_each(|(path, shape)| self.set_subpath(path, shape));
|
||||
}
|
||||
|
||||
pub fn common_layer_path_prefix<'a>(&self, layers: impl Iterator<Item = &'a [LayerId]>) -> &'a [LayerId] {
|
||||
layers.reduce(|a, b| &a[..a.iter().zip(b.iter()).take_while(|&(a, b)| a == b).count()]).unwrap_or_default()
|
||||
}
|
||||
@@ -865,6 +826,12 @@ impl Document {
|
||||
}
|
||||
Some(vec![DocumentChanged, LayerChanged { path }])
|
||||
}
|
||||
Operation::SetVectorData { path, vector_data } => {
|
||||
if let LayerDataType::NodeGraphFrame(graph) = &mut self.layer_mut(&path)?.data {
|
||||
graph.vector_data = Some(vector_data);
|
||||
}
|
||||
Some(Vec::new())
|
||||
}
|
||||
Operation::InsertManipulatorGroup {
|
||||
layer_path,
|
||||
manipulator_group,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::boolean_ops::{split_path_seg, subdivide_path_seg};
|
||||
use crate::consts::{F64LOOSE, F64PRECISE};
|
||||
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
@@ -19,6 +20,13 @@ impl Quad {
|
||||
Self([bbox[0], bbox[0] + size * DVec2::X, bbox[1], bbox[0] + size * DVec2::Y])
|
||||
}
|
||||
|
||||
/// Get all the edges in the quad.
|
||||
pub fn lines_glam(&self) -> impl Iterator<Item = bezier_rs::Bezier> + '_ {
|
||||
[[self.0[0], self.0[1]], [self.0[1], self.0[2]], [self.0[2], self.0[3]], [self.0[3], self.0[0]]]
|
||||
.into_iter()
|
||||
.map(|[start, end]| bezier_rs::Bezier::from_linear_dvec2(start, end))
|
||||
}
|
||||
|
||||
/// Get all the edges in the quad.
|
||||
pub fn lines(&self) -> [Line; 4] {
|
||||
[
|
||||
@@ -101,6 +109,35 @@ pub fn intersect_quad_bez_path(quad: Quad, shape: &BezPath, filled: bool) -> boo
|
||||
get_arbitrary_point_on_path(&shape).map(|shape_point| quad.path().contains(shape_point)).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn intersect_quad_subpath(quad: Quad, subpath: &bezier_rs::Subpath<ManipulatorGroupId>, close_subpath: bool) -> bool {
|
||||
let mut subpath = subpath.clone();
|
||||
|
||||
// For close_subpath shapes act like shape was closed even if it isn't
|
||||
if close_subpath && !subpath.closed() {
|
||||
subpath.set_closed(true);
|
||||
}
|
||||
|
||||
// Check if outlines intersect
|
||||
if subpath
|
||||
.iter()
|
||||
.any(|path_segment| quad.lines_glam().any(|line| !path_segment.intersections(&line, None, None).is_empty()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Check if selection is entirely within the shape
|
||||
if close_subpath && subpath.contains_point(quad.center()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if shape is entirely within selection
|
||||
subpath
|
||||
.manipulator_groups()
|
||||
.first()
|
||||
.map(|group| group.anchor)
|
||||
.map(|shape_point| quad.path().contains(to_point(shape_point)))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns a point on `path`.
|
||||
/// This function will usually return the first point from the path's first segment, but callers should not rely on this behavior.
|
||||
pub fn get_arbitrary_point_on_path(path: &BezPath) -> Option<Point> {
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::intersection::Quad;
|
||||
use crate::DocumentError;
|
||||
use crate::LayerId;
|
||||
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use core::fmt;
|
||||
@@ -437,16 +438,9 @@ impl Layer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_subpath(&self) -> Option<&Subpath> {
|
||||
pub fn as_vector_data(&self) -> Option<&VectorData> {
|
||||
match &self.data {
|
||||
LayerDataType::Shape(s) => Some(&s.shape),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_subpath_copy(&self) -> Option<Subpath> {
|
||||
match &self.data {
|
||||
LayerDataType::Shape(s) => Some(s.shape.clone()),
|
||||
LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(vector_data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -503,10 +497,18 @@ impl Layer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_graph_frame(&self) -> Result<&NodeGraphFrameLayer, DocumentError> {
|
||||
match &self.data {
|
||||
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
|
||||
_ => Err(DocumentError::NotNodeGraph),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn style(&self) -> Result<&PathStyle, DocumentError> {
|
||||
match &self.data {
|
||||
LayerDataType::Shape(s) => Ok(&s.style),
|
||||
LayerDataType::Text(t) => Ok(&t.path_style),
|
||||
LayerDataType::NodeGraphFrame(t) => t.vector_data.as_ref().map(|vector| &vector.style).ok_or(DocumentError::NotShape),
|
||||
_ => Err(DocumentError::NotShape),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use super::base64_serde;
|
||||
use super::layer_info::LayerData;
|
||||
use super::style::{RenderData, ViewMode};
|
||||
use crate::intersection::{intersect_quad_bez_path, Quad};
|
||||
use crate::intersection::{intersect_quad_bez_path, intersect_quad_subpath, Quad};
|
||||
use crate::LayerId;
|
||||
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_core::vector::VectorData;
|
||||
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Write;
|
||||
@@ -23,6 +24,7 @@ pub struct NodeGraphFrameLayer {
|
||||
#[serde(skip)]
|
||||
pub dimensions: DVec2,
|
||||
pub image_data: Option<ImageData>,
|
||||
pub vector_data: Option<VectorData>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, specta::Type)]
|
||||
@@ -33,7 +35,7 @@ pub struct ImageData {
|
||||
}
|
||||
|
||||
impl LayerData for NodeGraphFrameLayer {
|
||||
fn render(&mut self, svg: &mut String, _svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: &RenderData) -> bool {
|
||||
fn render(&mut self, svg: &mut String, svg_defs: &mut String, transforms: &mut Vec<DAffine2>, render_data: &RenderData) -> bool {
|
||||
let transform = self.transform(transforms, render_data.view_mode);
|
||||
let inverse = transform.inverse();
|
||||
|
||||
@@ -56,7 +58,21 @@ impl LayerData for NodeGraphFrameLayer {
|
||||
.enumerate()
|
||||
.fold(String::new(), |val, (i, entry)| val + &(entry.to_string() + if i == 5 { "" } else { "," }));
|
||||
|
||||
if let Some(blob_url) = &self.blob_url {
|
||||
// Render any paths if they exist
|
||||
if let Some(vector_data) = &self.vector_data {
|
||||
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
|
||||
let transfomed_bounds = vector_data.bounding_box_with_transform(transform).unwrap_or_default();
|
||||
|
||||
let _ = write!(svg, "<path d=\"");
|
||||
for subpath in &vector_data.subpaths {
|
||||
let _ = subpath.subpath_to_svg(svg, transform);
|
||||
}
|
||||
svg.push('"');
|
||||
|
||||
svg.push_str(&vector_data.style.render(render_data.view_mode, svg_defs, transform, layer_bounds, transfomed_bounds));
|
||||
let _ = write!(svg, "/>");
|
||||
} else if let Some(blob_url) = &self.blob_url {
|
||||
// Render the image if it exists
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<image width="{}" height="{}" preserveAspectRatio="none" href="{}" transform="matrix({})" />"#,
|
||||
@@ -66,6 +82,7 @@ impl LayerData for NodeGraphFrameLayer {
|
||||
matrix
|
||||
);
|
||||
} else {
|
||||
// Render a dotted blue outline if there is no image or vector data
|
||||
let _ = write!(
|
||||
svg,
|
||||
r#"<rect width="{}" height="{}" fill="none" stroke="var(--color-data-vector)" stroke-width="3" stroke-dasharray="8" transform="matrix({})" />"#,
|
||||
@@ -81,6 +98,10 @@ impl LayerData for NodeGraphFrameLayer {
|
||||
}
|
||||
|
||||
fn bounding_box(&self, transform: glam::DAffine2, _render_data: &RenderData) -> Option<[DVec2; 2]> {
|
||||
if let Some(vector_data) = &self.vector_data {
|
||||
return vector_data.bounding_box_with_transform(transform);
|
||||
}
|
||||
|
||||
let mut path = self.bounds();
|
||||
|
||||
if transform.matrix2 == DMat2::ZERO {
|
||||
@@ -93,7 +114,12 @@ impl LayerData for NodeGraphFrameLayer {
|
||||
}
|
||||
|
||||
fn intersects_quad(&self, quad: Quad, path: &mut Vec<LayerId>, intersections: &mut Vec<Vec<LayerId>>, _render_data: &RenderData) {
|
||||
if intersect_quad_bez_path(quad, &self.bounds(), true) {
|
||||
if let Some(vector_data) = &self.vector_data {
|
||||
let filled_style = vector_data.style.fill().is_some();
|
||||
if vector_data.subpaths.iter().any(|subpath| intersect_quad_subpath(quad, subpath, filled_style || subpath.closed())) {
|
||||
intersections.push(path.clone());
|
||||
}
|
||||
} else if intersect_quad_bez_path(quad, &self.bounds(), true) {
|
||||
intersections.push(path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,6 +187,10 @@ pub enum Operation {
|
||||
path: Vec<LayerId>,
|
||||
subpath: Subpath,
|
||||
},
|
||||
SetVectorData {
|
||||
path: Vec<LayerId>,
|
||||
vector_data: graphene_core::vector::VectorData,
|
||||
},
|
||||
InsertManipulatorGroup {
|
||||
layer_path: Vec<LayerId>,
|
||||
manipulator_group: ManipulatorGroup,
|
||||
|
||||
@@ -36,6 +36,9 @@ pub enum DocumentMessage {
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
NodeGraph(NodeGraphMessage),
|
||||
#[remain::unsorted]
|
||||
#[child]
|
||||
GraphOperation(GraphOperationMessage),
|
||||
|
||||
// Messages
|
||||
AbortTransaction,
|
||||
@@ -62,9 +65,7 @@ pub enum DocumentMessage {
|
||||
layer_path: Vec<LayerId>,
|
||||
},
|
||||
DeleteSelectedLayers,
|
||||
DeleteSelectedManipulatorPoints,
|
||||
DeselectAllLayers,
|
||||
DeselectAllManipulatorPoints,
|
||||
DirtyRenderDocument,
|
||||
DirtyRenderDocumentInOutlineView,
|
||||
DocumentHistoryBackward,
|
||||
@@ -93,11 +94,6 @@ pub enum DocumentMessage {
|
||||
insert_index: isize,
|
||||
reverse_index: bool,
|
||||
},
|
||||
MoveSelectedManipulatorPoints {
|
||||
layer_path: Vec<LayerId>,
|
||||
delta: (f64, f64),
|
||||
mirror_distance: bool,
|
||||
},
|
||||
NodeGraphFrameClear {
|
||||
layer_path: Vec<LayerId>,
|
||||
node_id: NodeId,
|
||||
@@ -193,10 +189,6 @@ pub enum DocumentMessage {
|
||||
ToggleLayerVisibility {
|
||||
layer_path: Vec<LayerId>,
|
||||
},
|
||||
ToggleSelectedHandleMirroring {
|
||||
layer_path: Vec<LayerId>,
|
||||
toggle_angle: bool,
|
||||
},
|
||||
Undo,
|
||||
UndoFinished,
|
||||
UngroupLayers {
|
||||
|
||||
@@ -33,7 +33,6 @@ use document_legacy::layers::text_layer::Font;
|
||||
use document_legacy::{DocumentError, DocumentResponse, LayerId, Operation as DocumentOperation};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_core::raster::{Color, ImageFrame};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -207,6 +206,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
let selected_layers = &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice()));
|
||||
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, selected_layers));
|
||||
}
|
||||
#[remain::unsorted]
|
||||
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
|
||||
|
||||
// Messages
|
||||
AbortTransaction => {
|
||||
@@ -252,13 +253,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
_ => 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.add(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(translation),
|
||||
transform_in: TransformIn::Viewport,
|
||||
});
|
||||
}
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
}
|
||||
@@ -323,25 +322,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.push_front(BroadcastEvent::SelectionChanged.into());
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
}
|
||||
DeleteSelectedManipulatorPoints => {
|
||||
responses.push_back(StartTransaction.into());
|
||||
|
||||
responses.push_front(
|
||||
DocumentOperation::DeleteSelectedManipulatorPoints {
|
||||
layer_paths: self.selected_layers_without_children().iter().map(|path| path.to_vec()).collect(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
DeselectAllLayers => {
|
||||
responses.push_front(SetSelectedLayers { replacement_selected_layers: vec![] }.into());
|
||||
self.layer_range_selection_reference.clear();
|
||||
}
|
||||
DeselectAllManipulatorPoints => {
|
||||
for layer_path in self.selected_layers_without_children() {
|
||||
responses.push_back(DocumentOperation::DeselectAllManipulatorPoints { layer_path: layer_path.to_vec() }.into());
|
||||
}
|
||||
}
|
||||
DirtyRenderDocument => {
|
||||
// Mark all non-overlay caches as dirty
|
||||
DocumentLegacy::mark_children_as_dirty(&mut self.document_legacy.root);
|
||||
@@ -409,14 +393,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
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.add(GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_scale(scale),
|
||||
transform_in: TransformIn::Scope { scope: bbox_trans },
|
||||
});
|
||||
}
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
}
|
||||
@@ -507,11 +488,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
MoveSelectedManipulatorPoints { layer_path, delta, mirror_distance } => {
|
||||
if let Ok(_layer) = self.document_legacy.layer(&layer_path) {
|
||||
responses.push_back(DocumentOperation::MoveSelectedManipulatorPoints { layer_path, delta, mirror_distance }.into());
|
||||
}
|
||||
}
|
||||
NodeGraphFrameClear {
|
||||
layer_path,
|
||||
node_id,
|
||||
@@ -577,7 +553,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
for path in self.selected_layers().map(|path| path.to_vec()) {
|
||||
// Nudge translation
|
||||
let transform = if !ipp.keyboard.key(resize) {
|
||||
Some(DAffine2::from_translation((delta_x, delta_y).into()).to_cols_array())
|
||||
Some(DAffine2::from_translation((delta_x, delta_y).into()))
|
||||
}
|
||||
// Nudge resize
|
||||
else {
|
||||
@@ -595,12 +571,13 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
let offset = DAffine2::from_translation(if opposite_corner { -existing_bottom_right } else { -existing_top_left });
|
||||
let scale = DAffine2::from_scale((new_width / width, new_height / height).into());
|
||||
|
||||
(offset.inverse() * scale * offset).to_cols_array()
|
||||
offset.inverse() * scale * offset
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(transform) = transform {
|
||||
responses.push_back(DocumentOperation::TransformLayerInViewport { path, transform }.into());
|
||||
let transform_in = TransformIn::Viewport;
|
||||
responses.add(GraphOperationMessage::TransformChange { layer: path, transform, transform_in });
|
||||
}
|
||||
}
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
@@ -662,13 +639,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
.into(),
|
||||
);
|
||||
|
||||
responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.clone(),
|
||||
transform: transform.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: path.clone(),
|
||||
transform,
|
||||
transform_in: TransformIn::Local,
|
||||
});
|
||||
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate { layer_path: path }.into());
|
||||
|
||||
@@ -920,9 +895,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.push_back(DocumentOperation::ToggleLayerVisibility { path: layer_path }.into());
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
}
|
||||
ToggleSelectedHandleMirroring { layer_path, toggle_angle } => {
|
||||
responses.push_back(DocumentOperation::SetSelectedHandleMirroring { layer_path, toggle_angle }.into());
|
||||
}
|
||||
Undo => {
|
||||
self.undo_in_progress = true;
|
||||
responses.push_back(BroadcastEvent::ToolAbort.into());
|
||||
@@ -1250,22 +1222,6 @@ impl DocumentMessageHandler {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a copy of all the currently selected [Subpath]s.
|
||||
pub fn selected_subpaths(&self) -> Vec<Subpath> {
|
||||
self.selected_visible_layers()
|
||||
.flat_map(|layer| self.document_legacy.layer(layer))
|
||||
.flat_map(|layer| layer.as_subpath_copy())
|
||||
.collect::<Vec<Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns references to all the currently selected [Subpath]s.
|
||||
pub fn selected_subpaths_ref(&self) -> Vec<&Subpath> {
|
||||
self.selected_visible_layers()
|
||||
.flat_map(|layer| self.document_legacy.layer(layer))
|
||||
.flat_map(|layer| layer.as_subpath())
|
||||
.collect::<Vec<&Subpath>>()
|
||||
}
|
||||
|
||||
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
|
||||
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>, render_data: &'a RenderData) -> impl Iterator<Item = [DVec2; 2]> + 'a {
|
||||
self.visible_layers()
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::{Fill, Stroke};
|
||||
use graphene_core::vector::ManipulatorPointId;
|
||||
|
||||
pub type LayerIdentifier = Vec<document_legacy::LayerId>;
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GraphOperationMessage {
|
||||
FillSet { layer: LayerIdentifier, fill: Fill },
|
||||
|
||||
StrokeSet { layer: LayerIdentifier, stroke: Stroke },
|
||||
|
||||
TransformChange { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn },
|
||||
TransformSet { layer: LayerIdentifier, transform: DAffine2, transform_in: TransformIn },
|
||||
TransformSetPivot { layer: LayerIdentifier, pivot: DVec2 },
|
||||
|
||||
Vector { layer: LayerIdentifier, modification: VectorDataModification },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum TransformIn {
|
||||
Local,
|
||||
Scope { scope: DAffine2 },
|
||||
Viewport,
|
||||
}
|
||||
|
||||
type ManipulatorGroup = bezier_rs::ManipulatorGroup<ManipulatorGroupId>;
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VectorDataModification {
|
||||
AddEndManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
|
||||
AddManipulatorGroup { manipulator_group: ManipulatorGroup, after_id: ManipulatorGroupId },
|
||||
AddStartManipulatorGroup { subpath_index: usize, manipulator_group: ManipulatorGroup },
|
||||
RemoveManipulatorGroup { id: ManipulatorGroupId },
|
||||
RemoveManipulatorPoint { point: ManipulatorPointId },
|
||||
SetClosed { index: usize, closed: bool },
|
||||
SetManipulatorHandleMirroring { id: ManipulatorGroupId, mirror_angle: bool },
|
||||
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
|
||||
ToggleManipulatorHandleMirroring { id: ManipulatorGroupId },
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::vector::style::{Fill, FillType, Stroke};
|
||||
use transform_utils::LayerBounds;
|
||||
|
||||
use super::{resolve_document_node_type, VectorDataModification};
|
||||
|
||||
mod transform_utils;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct GraphOperationMessageHandler;
|
||||
|
||||
struct ModifyInputsContext<'a> {
|
||||
network: &'a mut NodeNetwork,
|
||||
node_graph: &'a mut NodeGraphMessageHandler,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
layer: &'a [LayerId],
|
||||
}
|
||||
impl<'a> ModifyInputsContext<'a> {
|
||||
/// Get the node network from the document
|
||||
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
|
||||
document.layer_mut(&layer).ok().and_then(|layer| layer.as_node_graph_mut().ok()).map(|network| Self {
|
||||
network,
|
||||
node_graph,
|
||||
responses,
|
||||
layer,
|
||||
})
|
||||
}
|
||||
|
||||
/// Updates the input of an existing node
|
||||
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let document_node = self.network.nodes.get_mut(&node_id).unwrap();
|
||||
update_input(&mut document_node.inputs);
|
||||
}
|
||||
/// Insert a new node and modify the inputs
|
||||
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let output_node_id = self.network.outputs[0].node_id;
|
||||
let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else {
|
||||
warn!("Output node doesn't exist");
|
||||
return;
|
||||
};
|
||||
|
||||
let metadata = output_node.metadata.clone();
|
||||
let new_input = output_node.inputs[0].clone();
|
||||
let node_id = generate_uuid();
|
||||
output_node.inputs[0] = NodeInput::node(node_id, 0);
|
||||
|
||||
let Some(node_type) = resolve_document_node_type(name) else {
|
||||
warn!("Node type \"{name}\" doesn't exist");
|
||||
return;
|
||||
};
|
||||
let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata);
|
||||
update_input(&mut new_document_node.inputs);
|
||||
self.network.nodes.insert(node_id, new_document_node);
|
||||
}
|
||||
|
||||
/// Changes the inputs of a specific node
|
||||
fn modify_inputs(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
|
||||
let node_id = self.network.primary_flow().find(|(node, _)| node.name == name).map(|(_, id)| id);
|
||||
if let Some(node_id) = node_id {
|
||||
self.modify_existing_node_inputs(node_id, update_input);
|
||||
} else {
|
||||
self.modify_new_node(name, update_input);
|
||||
}
|
||||
self.node_graph.layer_path = Some(self.layer.to_vec());
|
||||
self.node_graph.nested_path.clear();
|
||||
self.responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
let layer_path = self.layer.to_vec();
|
||||
self.responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path });
|
||||
}
|
||||
fn fill_set(&mut self, fill: Fill) {
|
||||
self.modify_inputs("Fill", |inputs| {
|
||||
let fill_type = match fill {
|
||||
Fill::None => FillType::None,
|
||||
Fill::Solid(_) => FillType::Solid,
|
||||
Fill::Gradient(_) => FillType::Gradient,
|
||||
};
|
||||
inputs[1] = NodeInput::value(TaggedValue::FillType(fill_type), false);
|
||||
if Fill::None == fill {
|
||||
inputs[2] = NodeInput::value(TaggedValue::OptionalColor(None), false);
|
||||
} else if let Fill::Solid(color) = fill {
|
||||
inputs[2] = NodeInput::value(TaggedValue::OptionalColor(Some(color)), false);
|
||||
} else if let Fill::Gradient(gradient) = fill {
|
||||
inputs[3] = NodeInput::value(TaggedValue::GradientType(gradient.gradient_type), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::DVec2(gradient.start), false);
|
||||
inputs[5] = NodeInput::value(TaggedValue::DVec2(gradient.end), false);
|
||||
inputs[6] = NodeInput::value(TaggedValue::DAffine2(gradient.transform), false);
|
||||
inputs[7] = NodeInput::value(TaggedValue::GradientPositions(gradient.positions), false);
|
||||
}
|
||||
});
|
||||
}
|
||||
fn stroke_set(&mut self, stroke: Stroke) {
|
||||
self.modify_inputs("Stroke", |inputs| {
|
||||
inputs[1] = NodeInput::value(TaggedValue::Color(stroke.color.unwrap_or_default()), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F64(stroke.weight), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::VecF32(stroke.dash_lengths), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::F64(stroke.dash_offset), false);
|
||||
inputs[5] = NodeInput::value(TaggedValue::LineCap(stroke.line_cap), false);
|
||||
inputs[6] = NodeInput::value(TaggedValue::LineJoin(stroke.line_join), false);
|
||||
inputs[7] = NodeInput::value(TaggedValue::F64(stroke.line_join_miter_limit), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) {
|
||||
self.modify_inputs("Transform", |inputs| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let to = match transform_in {
|
||||
TransformIn::Local => DAffine2::IDENTITY,
|
||||
TransformIn::Scope { scope } => scope * parent_transform,
|
||||
TransformIn::Viewport => parent_transform,
|
||||
};
|
||||
let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
|
||||
let transform = to.inverse() * pivot.inverse() * transform * pivot * to * layer_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds) {
|
||||
self.modify_inputs("Transform", |inputs| {
|
||||
let to = match transform_in {
|
||||
TransformIn::Local => DAffine2::IDENTITY,
|
||||
TransformIn::Scope { scope } => scope * parent_transform,
|
||||
TransformIn::Viewport => parent_transform,
|
||||
};
|
||||
let pivot = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
|
||||
let transform = to.inverse() * pivot.inverse() * transform * pivot;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
|
||||
self.modify_inputs("Transform", |inputs| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let old_pivot_transform = DAffine2::from_translation(bounds.local_pivot(transform_utils::get_current_normalised_pivot(inputs)));
|
||||
let new_pivot_transform = DAffine2::from_translation(bounds.local_pivot(new_pivot));
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
||||
});
|
||||
}
|
||||
|
||||
fn vector_modify(&mut self, modification: VectorDataModification) {
|
||||
let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
|
||||
|
||||
self.modify_inputs("Path Generator", |inputs| {
|
||||
let [subpaths, mirror_angle_groups] = inputs.as_mut_slice() else {
|
||||
panic!("Path generator does not have subpath and mirror angle inputs");
|
||||
};
|
||||
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Subpaths(subpaths),
|
||||
..
|
||||
} = subpaths else{
|
||||
return;
|
||||
};
|
||||
let NodeInput::Value {
|
||||
tagged_value: TaggedValue::ManipulatorGroupIds(mirror_angle_groups),
|
||||
..
|
||||
} = mirror_angle_groups else{
|
||||
return;
|
||||
};
|
||||
|
||||
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||
|
||||
transform_utils::VectorModificationState { subpaths, mirror_angle_groups }.modify(modification);
|
||||
|
||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
||||
});
|
||||
self.modify_inputs("Transform", |inputs| {
|
||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||
let normalised_pivot = transform_utils::get_current_normalised_pivot(inputs);
|
||||
|
||||
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalised_pivot + old_bounds_min;
|
||||
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalised_pivot + new_bounds_min;
|
||||
let new_pivot_transform = DAffine2::from_translation(new_layerspace_pivot);
|
||||
let old_pivot_transform = DAffine2::from_translation(old_layerspace_pivot);
|
||||
|
||||
let transform = new_pivot_transform.inverse() * old_pivot_transform * layer_transform * old_pivot_transform.inverse() * new_pivot_transform;
|
||||
transform_utils::update_transform(inputs, transform);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
|
||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, (document, node_graph): (&mut Document, &mut NodeGraphMessageHandler)) {
|
||||
match message {
|
||||
GraphOperationMessage::FillSet { layer, fill } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.fill_set(fill);
|
||||
} else {
|
||||
responses.add(Operation::SetLayerFill { path: layer, fill });
|
||||
}
|
||||
}
|
||||
|
||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.stroke_set(stroke);
|
||||
} else {
|
||||
responses.add(Operation::SetLayerStroke { path: layer, stroke });
|
||||
}
|
||||
}
|
||||
|
||||
GraphOperationMessage::TransformChange { layer, transform, transform_in } => {
|
||||
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds);
|
||||
} else {
|
||||
let transform = transform.to_cols_array();
|
||||
responses.add(match transform_in {
|
||||
TransformIn::Local => Operation::TransformLayer { path: layer, transform },
|
||||
TransformIn::Scope { scope } => {
|
||||
let scope = scope.to_cols_array();
|
||||
Operation::TransformLayerInScope { path: layer, transform, scope }
|
||||
}
|
||||
TransformIn::Viewport => Operation::TransformLayerInViewport { path: layer, transform },
|
||||
});
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSet { layer, transform, transform_in } => {
|
||||
let parent_transform = document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, bounds);
|
||||
} else {
|
||||
let transform = transform.to_cols_array();
|
||||
responses.add(match transform_in {
|
||||
TransformIn::Local => Operation::SetLayerTransform { path: layer, transform },
|
||||
TransformIn::Scope { scope } => {
|
||||
let scope = scope.to_cols_array();
|
||||
Operation::SetLayerTransformInScope { path: layer, transform, scope }
|
||||
}
|
||||
TransformIn::Viewport => Operation::SetLayerTransformInViewport { path: layer, transform },
|
||||
});
|
||||
}
|
||||
}
|
||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.pivot_set(pivot, bounds);
|
||||
}
|
||||
|
||||
let pivot = pivot.into();
|
||||
responses.add(Operation::SetPivot { layer_path: layer, pivot });
|
||||
}
|
||||
|
||||
GraphOperationMessage::Vector { layer, modification } => {
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new(&layer, document, node_graph, responses) {
|
||||
modify_inputs.vector_modify(modification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(GraphOperationMessage; )
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use document_legacy::document::Document;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::{value::TaggedValue, NodeInput};
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
/// Convert an affine transform into scale angle translation and shear, assuming shear.y = 0.
|
||||
pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {
|
||||
let x_axis = transform.matrix2.x_axis;
|
||||
let y_axis = transform.matrix2.y_axis;
|
||||
|
||||
// Assuming there is no vertical shear
|
||||
let angle = x_axis.y.atan2(x_axis.x);
|
||||
let (sin, cos) = angle.sin_cos();
|
||||
let scale_x = if cos.abs() > 1e-10 { x_axis.x / cos } else { x_axis.y / sin };
|
||||
|
||||
let mut shear_x = (sin * y_axis.y + cos * y_axis.x) / (sin * sin * scale_x + cos * cos * scale_x);
|
||||
if !shear_x.is_finite() {
|
||||
shear_x = 0.;
|
||||
}
|
||||
let scale_y = if cos.abs() > 1e-10 {
|
||||
(y_axis.y - scale_x * sin * shear_x) / cos
|
||||
} else {
|
||||
(scale_x * cos * shear_x - y_axis.x) / sin
|
||||
};
|
||||
let translation = transform.translation;
|
||||
let scale = DVec2::new(scale_x, scale_y);
|
||||
let shear = DVec2::new(shear_x, 0.);
|
||||
(scale, angle, translation, shear)
|
||||
}
|
||||
|
||||
/// Update the inputs of the transform node to match a new transform
|
||||
pub fn update_transform(inputs: &mut [NodeInput], transform: DAffine2) {
|
||||
let (scale, angle, translation, skew) = compute_scale_angle_translation_shear(transform);
|
||||
|
||||
inputs[1] = NodeInput::value(TaggedValue::DVec2(translation), false);
|
||||
inputs[2] = NodeInput::value(TaggedValue::F64(angle), false);
|
||||
inputs[3] = NodeInput::value(TaggedValue::DVec2(scale), false);
|
||||
inputs[4] = NodeInput::value(TaggedValue::DVec2(skew), false);
|
||||
}
|
||||
|
||||
/// TODO: This should be extracted from the graph at the location of the transform node.
|
||||
pub struct LayerBounds {
|
||||
pub bounds: [DVec2; 2],
|
||||
pub bounds_transform: DAffine2,
|
||||
pub layer_transform: DAffine2,
|
||||
}
|
||||
|
||||
impl LayerBounds {
|
||||
/// Extract the layer bounds and their transform for a layer.
|
||||
pub fn new(document: &Document, layer_path: &[u64]) -> Self {
|
||||
let layer = document.layer(layer_path).ok();
|
||||
let bounds = layer
|
||||
.and_then(|layer| layer.as_graph_frame().ok())
|
||||
.and_then(|frame| frame.vector_data.as_ref().map(|vector| vector.nonzero_bounding_box()))
|
||||
.unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
let bounds_transform = DAffine2::IDENTITY;
|
||||
let layer_transform = document.multiply_transforms(layer_path).unwrap_or_default();
|
||||
Self {
|
||||
bounds,
|
||||
bounds_transform,
|
||||
layer_transform,
|
||||
}
|
||||
}
|
||||
pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
self.bounds[0] + (self.bounds[1] - self.bounds[0]) * normalised_pivot
|
||||
}
|
||||
pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
self.bounds_transform.transform_point2(self.layerspace_pivot(normalised_pivot))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current affine transform from the transform node's inputs
|
||||
pub fn get_current_transform(inputs: &[NodeInput]) -> DAffine2 {
|
||||
let translation = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(translation),
|
||||
..
|
||||
} = inputs[1]
|
||||
{
|
||||
translation
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
};
|
||||
let angle = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::F64(angle),
|
||||
..
|
||||
} = inputs[2]
|
||||
{
|
||||
angle
|
||||
} else {
|
||||
0.
|
||||
};
|
||||
let scale = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(scale),
|
||||
..
|
||||
} = inputs[3]
|
||||
{
|
||||
scale
|
||||
} else {
|
||||
DVec2::ONE
|
||||
};
|
||||
let shear = if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(shear),
|
||||
..
|
||||
} = inputs[4]
|
||||
{
|
||||
shear
|
||||
} else {
|
||||
DVec2::ZERO
|
||||
};
|
||||
DAffine2::from_scale_angle_translation(scale, angle, translation) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.])
|
||||
}
|
||||
|
||||
/// Extract the current normalised pivot from the layer
|
||||
pub fn get_current_normalised_pivot(inputs: &[NodeInput]) -> DVec2 {
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(pivot),
|
||||
..
|
||||
} = inputs[5]
|
||||
{
|
||||
pivot
|
||||
} else {
|
||||
DVec2::splat(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
// \begin{bmatrix}
|
||||
// S_{x}\cos(\theta)-S_{y}\sin(\theta)H_{y} & S_{x}\cos(\theta)H_{x}-S_{y}\sin(\theta) & T_{x}\\
|
||||
// S_{x}\sin(\theta)+S_{y}\cos(\theta)H_{y} & S_{x}\sin(\theta)H_{x}+S_{y}\cos(\theta) & T_{y}\\
|
||||
// 0 & 0 & 1
|
||||
// \end{bmatrix}
|
||||
#[test]
|
||||
fn derive_transform() {
|
||||
for shear_x in -10..=10 {
|
||||
let shear_x = (shear_x as f64) / 2.;
|
||||
for angle in (0..=360).step_by(15) {
|
||||
let angle = (angle as f64).to_radians();
|
||||
for scale_x in 1..10 {
|
||||
let scale_x = (scale_x as f64) / 5.;
|
||||
for scale_y in 1..10 {
|
||||
let scale_y = (scale_y as f64) / 5.;
|
||||
|
||||
let shear = DVec2::new(shear_x, 0.);
|
||||
let scale = DVec2::new(scale_x, scale_y);
|
||||
let translate = DVec2::new(5666., 644.);
|
||||
|
||||
let origional_transform = DAffine2::from_cols(
|
||||
DVec2::new(scale.x * angle.cos() - scale.y * angle.sin() * shear.y, scale.x * angle.sin() + scale.y * angle.cos() * shear.y),
|
||||
DVec2::new(scale.x * angle.cos() * shear.x - scale.y * angle.sin(), scale.x * angle.sin() * shear.x + scale.y * angle.cos()),
|
||||
translate,
|
||||
);
|
||||
|
||||
let (new_scale, new_angle, new_translation, new_shear) = compute_scale_angle_translation_shear(origional_transform);
|
||||
let new_transform = DAffine2::from_scale_angle_translation(new_scale, new_angle, new_translation) * DAffine2::from_cols_array(&[1., new_shear.y, new_shear.x, 1., 0., 0.]);
|
||||
|
||||
assert!(
|
||||
new_transform.abs_diff_eq(origional_transform, 1e-10),
|
||||
"origional_transform {} new_transform {} / scale {} new_scale {} / angle {} new_angle {} / shear {} / new_shear {}",
|
||||
origional_transform,
|
||||
new_transform,
|
||||
scale,
|
||||
new_scale,
|
||||
angle,
|
||||
new_angle,
|
||||
shear,
|
||||
new_shear,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Expand a bounds to avoid div zero errors
|
||||
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
/// Returns corners of all subpaths
|
||||
fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns corners of all subpaths (but expanded to avoid div zero errors)
|
||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||
clamp_bounds(bounds_min, bounds_max)
|
||||
}
|
||||
|
||||
pub struct VectorModificationState<'a> {
|
||||
pub subpaths: &'a mut Vec<Subpath<ManipulatorGroupId>>,
|
||||
pub mirror_angle_groups: &'a mut Vec<ManipulatorGroupId>,
|
||||
}
|
||||
impl<'a> VectorModificationState<'a> {
|
||||
fn insert_start(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
self.subpaths[subpath_index].insert_manipulator_group(0, manipulator_group)
|
||||
}
|
||||
|
||||
fn insert_end(&mut self, subpath_index: usize, manipulator_group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
let subpath = &mut self.subpaths[subpath_index];
|
||||
subpath.insert_manipulator_group(subpath.len(), manipulator_group)
|
||||
}
|
||||
fn insert(&mut self, manipulator_group: ManipulatorGroup<ManipulatorGroupId>, after_id: ManipulatorGroupId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(after_id) {
|
||||
subpath.insert_manipulator_group(index + 1, manipulator_group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn remove_group(&mut self, id: ManipulatorGroupId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(id) {
|
||||
subpath.remove_manipulator_group(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn remove_point(&mut self, point: ManipulatorPointId) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if point.manipulator_type == SelectedType::Anchor {
|
||||
if let Some(index) = subpath.manipulator_index_from_id(point.group) {
|
||||
subpath.remove_manipulator_group(index);
|
||||
break;
|
||||
}
|
||||
} else if let Some(group) = subpath.manipulator_mut_from_id(point.group) {
|
||||
if point.manipulator_type == SelectedType::InHandle {
|
||||
group.in_handle = None;
|
||||
} else if point.manipulator_type == SelectedType::OutHandle {
|
||||
group.out_handle = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn set_mirror(&mut self, id: ManipulatorGroupId, mirror_angle: bool) {
|
||||
if !mirror_angle {
|
||||
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
|
||||
} else if !self.mirror_angle_groups.contains(&id) {
|
||||
self.mirror_angle_groups.push(id);
|
||||
}
|
||||
}
|
||||
fn toggle_mirror(&mut self, id: ManipulatorGroupId) {
|
||||
if self.mirror_angle_groups.contains(&id) {
|
||||
self.mirror_angle_groups.retain(|&mirrored_id| mirrored_id != id);
|
||||
} else {
|
||||
self.mirror_angle_groups.push(id);
|
||||
}
|
||||
}
|
||||
fn set_position(&mut self, point: ManipulatorPointId, position: DVec2) {
|
||||
for subpath in self.subpaths.iter_mut() {
|
||||
if let Some(manipulator) = subpath.manipulator_mut_from_id(point.group) {
|
||||
match point.manipulator_type {
|
||||
SelectedType::Anchor => manipulator.anchor = position,
|
||||
SelectedType::InHandle => manipulator.in_handle = Some(position),
|
||||
SelectedType::OutHandle => manipulator.out_handle = Some(position),
|
||||
}
|
||||
if point.manipulator_type != SelectedType::Anchor && self.mirror_angle_groups.contains(&point.group) {
|
||||
let reflect = |opposite: DVec2| {
|
||||
(manipulator.anchor - position)
|
||||
.try_normalize()
|
||||
.map(|direction| direction * (opposite - manipulator.anchor).length() + manipulator.anchor)
|
||||
.unwrap_or(opposite)
|
||||
};
|
||||
match point.manipulator_type {
|
||||
SelectedType::InHandle => manipulator.out_handle = manipulator.out_handle.map(reflect),
|
||||
SelectedType::OutHandle => manipulator.in_handle = manipulator.in_handle.map(reflect),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn modify(&mut self, modification: VectorDataModification) {
|
||||
match modification {
|
||||
VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group } => self.insert_end(subpath_index, manipulator_group),
|
||||
VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group } => self.insert_start(subpath_index, manipulator_group),
|
||||
VectorDataModification::AddManipulatorGroup { manipulator_group, after_id } => self.insert(manipulator_group, after_id),
|
||||
VectorDataModification::RemoveManipulatorGroup { id } => self.remove_group(id),
|
||||
VectorDataModification::RemoveManipulatorPoint { point } => self.remove_point(point),
|
||||
VectorDataModification::SetClosed { index, closed } => self.subpaths[index].set_closed(closed),
|
||||
VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle } => self.set_mirror(id, mirror_angle),
|
||||
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
|
||||
VectorDataModification::ToggleManipulatorHandleMirroring { id } => self.toggle_mirror(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,3 +5,11 @@ mod node_graph_message_handler;
|
||||
pub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use node_graph_message_handler::*;
|
||||
|
||||
mod graph_operation_message;
|
||||
mod graph_operation_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use graph_operation_message::*;
|
||||
#[doc(inline)]
|
||||
pub use graph_operation_message_handler::*;
|
||||
|
||||
@@ -46,7 +46,7 @@ impl FrontendGraphDataType {
|
||||
TaggedValue::Image(_) => Self::Raster,
|
||||
TaggedValue::ImageFrame(_) => Self::Raster,
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::RcSubpath(_) | TaggedValue::Subpath(_) | TaggedValue::VectorData(_) => Self::Subpath,
|
||||
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
@@ -238,23 +238,7 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
// If empty, show all nodes in the network starting with the output
|
||||
if self.selected_nodes.is_empty() {
|
||||
let mut stack = network.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
let mut nodes = Vec::new();
|
||||
while let Some(node_id) = stack.pop() {
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
nodes.push((document_node, node_id));
|
||||
}
|
||||
for &(document_node, node_id) in nodes.iter().rev() {
|
||||
for (document_node, node_id) in network.primary_flow().collect::<Vec<_>>().into_iter().rev() {
|
||||
sections.push(node_properties::generate_node_properties(document_node, node_id, context));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,25 +668,25 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
DocumentNodeType {
|
||||
name: "Path Generator",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator"),
|
||||
inputs: vec![DocumentInputType {
|
||||
name: "Path Data",
|
||||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(bezier_rs::Subpath::new(Vec::new(), false)), false),
|
||||
}],
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
||||
DocumentInputType::value("Mirror", TaggedValue::ManipulatorGroupIds(vec![]), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Transform",
|
||||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _>"),
|
||||
identifier: NodeImplementation::proto("graphene_core::transform::TransformNode<_, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Translation", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Rotation", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::value("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
DocumentInputType::value("Pivot", TaggedValue::DVec2(DVec2::splat(0.5)), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::transform_properties,
|
||||
@@ -697,8 +697,8 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
identifier: NodeImplementation::proto("graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>"),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Vector Data", TaggedValue::VectorData(graphene_core::vector::VectorData::empty()), true),
|
||||
DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::Solid), false),
|
||||
DocumentInputType::value("Solid Color", TaggedValue::Color(Color::BLACK), false),
|
||||
DocumentInputType::value("Fill Type", TaggedValue::FillType(vector::style::FillType::None), false),
|
||||
DocumentInputType::value("Solid Color", TaggedValue::OptionalColor(None), false),
|
||||
DocumentInputType::value("Gradient Type", TaggedValue::GradientType(vector::style::GradientType::Linear), false),
|
||||
DocumentInputType::value("Start", TaggedValue::DVec2(DVec2::new(0., 0.5)), false),
|
||||
DocumentInputType::value("End", TaggedValue::DVec2(DVec2::new(1., 0.5)), false),
|
||||
@@ -871,7 +871,7 @@ pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetw
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_vector_network(subpath: bezier_rs::Subpath<uuid::ManipulatorGroupId>) -> NodeNetwork {
|
||||
pub fn new_vector_network(subpaths: Vec<bezier_rs::Subpath<uuid::ManipulatorGroupId>>) -> NodeNetwork {
|
||||
let input = resolve_document_node_type("Input").expect("Input node does not exist");
|
||||
let path_generator = resolve_document_node_type("Path Generator").expect("Path Generator node does not exist");
|
||||
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist");
|
||||
@@ -891,7 +891,7 @@ pub fn new_vector_network(subpath: bezier_rs::Subpath<uuid::ManipulatorGroupId>)
|
||||
outputs: vec![NodeOutput::new(5, 0)],
|
||||
nodes: [
|
||||
input.to_document_node_default_inputs([], next_pos()),
|
||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpath(subpath), false))], next_pos()),
|
||||
path_generator.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], next_pos()),
|
||||
transform.to_document_node_default_inputs([Some(NodeInput::node(1, 0))], next_pos()),
|
||||
fill.to_document_node_default_inputs([Some(NodeInput::node(2, 0))], next_pos()),
|
||||
stroke.to_document_node_default_inputs([Some(NodeInput::node(3, 0))], next_pos()),
|
||||
|
||||
@@ -398,18 +398,24 @@ fn gradient_positions(rows: &mut Vec<LayoutGroup>, document_node: &DocumentNode,
|
||||
fn color_widget(document_node: &DocumentNode, node_id: u64, index: usize, name: &str, color_props: ColorInput, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::Color(x),
|
||||
exposed: false,
|
||||
} = document_node.inputs[index]
|
||||
{
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
color_props
|
||||
.value(Some(x as Color))
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index))
|
||||
.widget_holder(),
|
||||
])
|
||||
if let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] {
|
||||
if let &TaggedValue::Color(x) = tagged_value {
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
color_props
|
||||
.value(Some(x as Color))
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.unwrap()), node_id, index))
|
||||
.widget_holder(),
|
||||
])
|
||||
} else if let &TaggedValue::OptionalColor(x) = tagged_value {
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
color_props
|
||||
.value(x)
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value), node_id, index))
|
||||
.widget_holder(),
|
||||
])
|
||||
}
|
||||
}
|
||||
LayoutGroup::Row { widgets }
|
||||
}
|
||||
@@ -574,7 +580,23 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||
let translation = {
|
||||
let index = 1;
|
||||
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, true);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, false);
|
||||
|
||||
let pivot_index = 5;
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(pivot),
|
||||
exposed: false,
|
||||
} = document_node.inputs[pivot_index]
|
||||
{
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
widgets.push(
|
||||
PivotAssist::new(pivot.into())
|
||||
.on_update(|pivot_assist: &PivotAssist| PropertiesPanelMessage::SetPivot { new_position: pivot_assist.position }.into())
|
||||
.widget_holder(),
|
||||
);
|
||||
} else {
|
||||
add_blank_assist(&mut widgets);
|
||||
}
|
||||
|
||||
if let NodeInput::Value {
|
||||
tagged_value: TaggedValue::DVec2(vec2),
|
||||
@@ -1044,7 +1066,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
||||
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).widget_holder(),
|
||||
DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).tooltip("When selecing a layer in a folder, shallow select will select the parent folder whereas deep select will select the layer. Double clicking in shallow select mode will select the layer.").widget_holder(),
|
||||
]);
|
||||
}
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Algorithm used to generate the image during each sampling step")
|
||||
@@ -1289,7 +1311,8 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
let mut widgets = Vec::new();
|
||||
let gradient = fill_type == Some(graphene_core::vector::style::FillType::Gradient);
|
||||
let solid = fill_type == Some(graphene_core::vector::style::FillType::Solid);
|
||||
if fill_type.is_none() || solid {
|
||||
let empty = fill_type == Some(graphene_core::vector::style::FillType::None);
|
||||
if fill_type.is_none() || solid || empty {
|
||||
let solid_color = color_widget(document_node, node_id, solid_color_index, "Color", ColorInput::default(), true);
|
||||
widgets.push(solid_color);
|
||||
}
|
||||
@@ -1300,7 +1323,7 @@ pub fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context:
|
||||
gradient_positions(&mut widgets, document_node, "Gradient Positions", node_id, positions_index);
|
||||
}
|
||||
|
||||
if gradient || solid {
|
||||
if gradient || solid || empty {
|
||||
let new_fill_type = if gradient { FillType::Solid } else { FillType::Gradient };
|
||||
let switch_button = TextButton::new(if gradient { "Use Solid Color" } else { "Use Gradient" })
|
||||
.tooltip(if gradient {
|
||||
|
||||
@@ -129,12 +129,12 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
||||
self.create_document_operation(Operation::SetTextContent { path, new_text }, true, responses);
|
||||
}
|
||||
SetPivot { new_position } => {
|
||||
let (layer_path, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
let (layer, _) = self.active_selection.clone().expect("Received update for properties panel with no active layer");
|
||||
let position: Option<glam::DVec2> = new_position.into();
|
||||
let pivot = position.unwrap().into();
|
||||
let pivot = position.unwrap();
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
responses.push_back(Operation::SetPivot { layer_path, pivot }.into());
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
CheckSelectedWasUpdated { path } => {
|
||||
if self.matches_selected(&path) {
|
||||
|
||||
@@ -4,7 +4,6 @@ use crate::messages::prelude::*;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation as DocumentOperation;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
@@ -152,6 +151,7 @@ impl TransformOperation {
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
self.hints(snapping, selected.responses);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +176,32 @@ impl TransformOperation {
|
||||
|
||||
self.apply_transform_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn hints(&self, snapping: bool, responses: &mut VecDeque<Message>) {
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
let mut hints = Vec::new();
|
||||
|
||||
let value_str = match self {
|
||||
TransformOperation::None => String::new(),
|
||||
TransformOperation::Grabbing(translation) => format!("Translate X: {} Y: {}", translation.to_dvec().x, translation.to_dvec().y),
|
||||
TransformOperation::Rotating(rotation) => format!("Rotate {}°", rotation.to_f64(snapping)),
|
||||
TransformOperation::Scaling(scale) => format!("Scale X: {} Y: {}", scale.to_dvec(snapping).x, scale.to_dvec(snapping).y),
|
||||
};
|
||||
hints.push(HintInfo::label(value_str));
|
||||
hints.push(HintInfo::keys([Key::Shift], "Precision Mode"));
|
||||
if matches!(self, TransformOperation::Rotating(_) | TransformOperation::Scaling(_)) {
|
||||
hints.push(HintInfo::keys([Key::Control], "Snap"));
|
||||
}
|
||||
if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) {
|
||||
hints.push(HintInfo::keys([Key::KeyX], "X Axis"));
|
||||
hints.push(HintInfo::keys([Key::KeyY], "Y Axis"));
|
||||
}
|
||||
|
||||
let hint_data = HintData(vec![HintGroup(hints)]);
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Selected<'a> {
|
||||
@@ -239,13 +265,11 @@ impl<'a> Selected<'a> {
|
||||
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer_path.to_vec(),
|
||||
transform: new,
|
||||
transform_in: TransformIn::Local,
|
||||
});
|
||||
}
|
||||
|
||||
self.responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
@@ -253,16 +277,14 @@ impl<'a> Selected<'a> {
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
for path in self.selected {
|
||||
if let Some(transform) = self.original_transforms.get(*path) {
|
||||
for layer in self.selected {
|
||||
if let Some(&transform) = self.original_transforms.get(*layer) {
|
||||
// Push front to stop document switching before sending the transform
|
||||
self.responses.push_front(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: path.to_vec(),
|
||||
transform: transform.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
self.responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer.to_vec(),
|
||||
transform,
|
||||
transform_in: TransformIn::Local,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPre
|
||||
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
||||
pub use crate::messages::portfolio::document::artboard::{ArtboardMessage, ArtboardMessageDiscriminant, ArtboardMessageHandler};
|
||||
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageDiscriminant, NavigationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::node_graph::{GraphOperationMessage, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
|
||||
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageDiscriminant, OverlaysMessageHandler};
|
||||
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
||||
@@ -50,7 +51,22 @@ pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextT
|
||||
|
||||
// Helper
|
||||
pub use crate::messages::globals::global_variables::*;
|
||||
pub use crate::messages::portfolio::document::node_graph::TransformIn;
|
||||
|
||||
pub use graphite_proc_macros::*;
|
||||
|
||||
pub use std::collections::{HashMap, HashSet, VecDeque};
|
||||
|
||||
pub trait Responses {
|
||||
fn add(&mut self, message: impl Into<Message>);
|
||||
fn try_add(&mut self, message: Option<impl Into<Message>>) {
|
||||
if let Some(message) = message {
|
||||
self.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Responses for VecDeque<Message> {
|
||||
fn add(&mut self, message: impl Into<Message>) {
|
||||
self.push_back(message.into());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use crate::messages::portfolio::document::node_graph;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use glam::DAffine2;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
||||
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let network = node_graph::new_vector_network(subpaths);
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNodeGraphFrame {
|
||||
path: layer_path.clone(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path });
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod graph_modification_utils;
|
||||
pub mod overlay_renderer;
|
||||
pub mod path_outline;
|
||||
pub mod pivot;
|
||||
|
||||
@@ -3,97 +3,106 @@ use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS;
|
||||
use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::style::{self, Fill, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use super::shape_editor::SelectedShapeState;
|
||||
|
||||
/// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor.
|
||||
type ManipulatorGroupOverlays = [Option<Vec<LayerId>>; 5];
|
||||
type ManipulatorId = u64;
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ManipulatorGroupOverlays {
|
||||
pub anchor: Option<Vec<LayerId>>,
|
||||
pub in_handle: Option<Vec<LayerId>>,
|
||||
pub in_line: Option<Vec<LayerId>>,
|
||||
pub out_handle: Option<Vec<LayerId>>,
|
||||
pub out_line: Option<Vec<LayerId>>,
|
||||
}
|
||||
impl ManipulatorGroupOverlays {
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Option<Vec<LayerId>>> {
|
||||
[&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
type GraphiteManipulatorGroup = ManipulatorGroup<ManipulatorGroupId>;
|
||||
|
||||
const POINT_STROKE_WEIGHT: f64 = 2.;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OverlayRenderer {
|
||||
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
|
||||
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>,
|
||||
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorGroupId), ManipulatorGroupOverlays>,
|
||||
}
|
||||
|
||||
impl OverlayRenderer {
|
||||
pub fn new() -> Self {
|
||||
OverlayRenderer {
|
||||
manipulator_group_overlay_cache: HashMap::new(),
|
||||
shape_overlay_cache: HashMap::new(),
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn render_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap();
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
let layer_id = layer_path.last().unwrap();
|
||||
self.layer_overlay_visibility(document, layer_path.clone(), true, responses);
|
||||
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
let outline_cache = self.shape_overlay_cache.get(layer_id);
|
||||
trace!("Overlay: Outline cache {:?}", &outline_cache);
|
||||
|
||||
// Create an outline if we do not have a cached one
|
||||
if outline_cache.is_none() {
|
||||
let outline_path = self.create_shape_outline_overlay(shape.clone(), responses);
|
||||
let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
|
||||
self.shape_overlay_cache.insert(*layer_id, outline_path.clone());
|
||||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
||||
trace!("Overlay: Creating new outline {:?}", &outline_path);
|
||||
} else if let Some(outline_path) = outline_cache {
|
||||
trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id);
|
||||
Self::modify_outline_overlays(outline_path.clone(), shape.clone(), responses);
|
||||
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
|
||||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
||||
}
|
||||
|
||||
// Create, place, and style the manipulator overlays
|
||||
for (manipulator_group_id, manipulator_group) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, *manipulator_group_id)).or_default();
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, manipulator_group.id)).or_default();
|
||||
|
||||
// Only view in and out handles if they are not on top of the anchor
|
||||
let [in_handle, out_handle] = {
|
||||
let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let anchor = manipulator_group.anchor;
|
||||
|
||||
let anchor_position = transform.transform_point2(anchor.position);
|
||||
let filter_position = |handle: &&ManipulatorPoint| transform.transform_point2(handle.position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
let filter_manipulator_point = |manipulator_type| manipulator_group.points[manipulator_type as usize].as_ref().filter(filter_position);
|
||||
[filter_manipulator_point(ManipulatorType::InHandle), filter_manipulator_point(ManipulatorType::OutHandle)]
|
||||
let anchor_position = transform.transform_point2(anchor);
|
||||
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
let filter_handle = |manipulator: Option<DVec2>| manipulator.filter(not_under_anchor);
|
||||
[filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)]
|
||||
};
|
||||
|
||||
// Create anchor
|
||||
manipulator_group_cache[0] = manipulator_group_cache[0].take().or_else(|| Some(Self::create_anchor_overlay(responses)));
|
||||
manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses)));
|
||||
// Create or delete in handle
|
||||
if in_handle.is_none() {
|
||||
Self::remove_overlay(manipulator_group_cache[1].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache[3].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.in_line.take(), responses);
|
||||
} else {
|
||||
manipulator_group_cache[1] = manipulator_group_cache[1].take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache[3] = manipulator_group_cache[3].take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
|
||||
}
|
||||
// Create or delete out handle
|
||||
if out_handle.is_none() {
|
||||
Self::remove_overlay(manipulator_group_cache[2].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache[4].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.out_line.take(), responses);
|
||||
} else {
|
||||
manipulator_group_cache[2] = manipulator_group_cache[2].take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache[4] = manipulator_group_cache[4].take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
|
||||
}
|
||||
|
||||
// Update placement and style
|
||||
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses);
|
||||
Self::style_overlays(manipulator_group, manipulator_group_cache, responses);
|
||||
Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses);
|
||||
}
|
||||
// TODO Handle removing shapes from cache so we don't memory leak
|
||||
// Eventually will get replaced with am immediate mode renderer for overlays
|
||||
@@ -112,11 +121,12 @@ impl OverlayRenderer {
|
||||
|
||||
// Remove the ManipulatorGroup overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let id = manipulator_group.id;
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
|
||||
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
|
||||
self.manipulator_group_overlay_cache.remove(&(*layer_id, *id));
|
||||
self.manipulator_group_overlay_cache.remove(&(*layer_id, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +143,10 @@ impl OverlayRenderer {
|
||||
|
||||
// Hide the manipulator group overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let id = manipulator_group.id;
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
|
||||
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
|
||||
}
|
||||
}
|
||||
@@ -144,7 +155,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create the kurbo shape that matches the selected viewport shape.
|
||||
fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
fn create_shape_outline_overlay(&self, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
@@ -185,7 +196,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create a single handle overlay and return its layer id if it exists.
|
||||
fn create_handle_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
fn create_handle_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.map(|_| Self::create_handle_overlay(responses))
|
||||
}
|
||||
|
||||
@@ -210,7 +221,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create the shape outline overlay and return its layer ID.
|
||||
fn create_handle_line_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
fn create_handle_line_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
|
||||
}
|
||||
|
||||
@@ -219,53 +230,48 @@ impl OverlayRenderer {
|
||||
responses.push_back(transform_message);
|
||||
}
|
||||
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: Subpath, responses: &mut VecDeque<Message>) {
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) {
|
||||
let outline_modify_message = Self::overlay_modify_message(outline_path, subpath);
|
||||
responses.push_back(outline_modify_message);
|
||||
}
|
||||
|
||||
/// Updates the position of the overlays based on the [Subpath] points.
|
||||
fn place_manipulator_group_overlays(manipulator_group: &ManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
if let Some(manipulator_point) = &manipulator_group.points[ManipulatorType::Anchor] {
|
||||
// Helper function to keep things DRY (don't-repeat-yourself)
|
||||
let mut place_handle_and_line = |handle: &ManipulatorPoint, line_overlay: &mut Vec<LayerId>, marker_source: &mut Option<Vec<LayerId>>| {
|
||||
let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position);
|
||||
let scale = DVec2::splat(line_vector.length());
|
||||
let angle = -line_vector.angle_between(DVec2::X);
|
||||
let translation = (parent_transform.transform_point2(handle.position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(line_overlay.clone(), transform));
|
||||
fn place_manipulator_group_overlays(manipulator_group: &GraphiteManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
let anchor = manipulator_group.anchor;
|
||||
let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option<Vec<LayerId>>| {
|
||||
let line_vector = parent_transform.transform_point2(anchor) - parent_transform.transform_point2(handle_position);
|
||||
let scale = DVec2::splat(line_vector.length());
|
||||
let angle = -line_vector.angle_between(DVec2::X);
|
||||
let translation = (parent_transform.transform_point2(handle_position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(line_overlay.to_vec(), transform));
|
||||
|
||||
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
|
||||
*marker_source = Some(marker_overlay);
|
||||
};
|
||||
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(handle_position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
|
||||
*marker_source = Some(marker_overlay);
|
||||
};
|
||||
|
||||
// Place the handle overlays
|
||||
let [_, h1, h2] = &manipulator_group.points;
|
||||
let [a, b, c, line1, line2] = overlays;
|
||||
let markers = [a, b, c];
|
||||
if let (Some(handle), Some(line_source)) = (h1.as_ref(), line1.as_mut()) {
|
||||
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
|
||||
}
|
||||
if let (Some(handle), Some(line_source)) = (h2.as_ref(), line2.as_mut()) {
|
||||
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
|
||||
}
|
||||
// Place the handle overlays
|
||||
if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.in_handle, overlays.in_line.as_mut()) {
|
||||
place_handle_and_line(handle_position, line_overlay, &mut overlays.in_handle);
|
||||
}
|
||||
if let (Some(handle_psoition), Some(line_overlay)) = (manipulator_group.out_handle, overlays.out_line.as_ref()) {
|
||||
place_handle_and_line(handle_psoition, line_overlay, &mut overlays.out_handle);
|
||||
}
|
||||
|
||||
// Place the anchor point overlay
|
||||
if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] {
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
// Place the anchor point overlay
|
||||
if let Some(anchor_overlay) = &overlays.anchor {
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(anchor) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
|
||||
responses.push_back(message);
|
||||
}
|
||||
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,24 +316,27 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create an update message for an overlay.
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: Subpath) -> Message {
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath) -> Message {
|
||||
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into()
|
||||
}
|
||||
|
||||
/// Sets the overlay style for this point.
|
||||
fn style_overlays(manipulator_group: &ManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
// TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar
|
||||
let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT));
|
||||
let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
|
||||
|
||||
let selected_shape_state = state.get(layer_path);
|
||||
// Update if the manipulator points are shown as selected
|
||||
// Here the index is important, even though overlays[..] has five elements we only care about the first three
|
||||
for (index, point) in manipulator_group.points.iter().enumerate() {
|
||||
if let Some(point) = point {
|
||||
if let Some(overlay) = &overlays[index] {
|
||||
let style = if point.editor_state.is_selected { selected_style.clone() } else { deselected_style.clone() };
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay.clone(), style }.into()).into());
|
||||
}
|
||||
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
|
||||
let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index];
|
||||
if let Some(overlay_path) = overlay {
|
||||
let selected = selected_shape_state
|
||||
.filter(|state| state.is_selected(ManipulatorPointId::new(manipulator_group.id, selected_type)))
|
||||
.is_some();
|
||||
|
||||
let style = if selected { selected_style.clone() } else { deselected_style.clone() };
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay_path.clone(), style }.into()).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::LayerDataType;
|
||||
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
@@ -35,6 +36,7 @@ impl PathOutline {
|
||||
let subpath = match &document_layer.data {
|
||||
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
|
||||
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(render_data)),
|
||||
LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(Subpath::from_bezier_crate(&vector_data.subpaths)),
|
||||
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
|
||||
}?;
|
||||
|
||||
|
||||
@@ -171,8 +171,8 @@ impl Pivot {
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
|
||||
if pivot.is_finite() {
|
||||
let layer_path = layer_path.to_owned();
|
||||
responses.push_back(Operation::SetPivot { layer_path, pivot: pivot.into() }.into());
|
||||
let layer = layer_path.to_owned();
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
|
||||
use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2, Vec2Swizzles};
|
||||
|
||||
@@ -54,9 +53,10 @@ impl Resize {
|
||||
}
|
||||
|
||||
Some(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: path.to_vec(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::TValue;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::{BezierId, Subpath};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use document_legacy::LayerId;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData};
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use glam::DVec2;
|
||||
|
||||
/// ShapeEditor is the container for all of the layer paths that are represented as [Subpath]s and provides
|
||||
/// functionality required to query and create the [Subpath] / [ManipulatorGroup]s / [ManipulatorPoint]s.
|
||||
///
|
||||
/// Overview:
|
||||
/// ```text
|
||||
/// ShapeEditor
|
||||
/// |
|
||||
/// selected_layers <- Paths to selected layers that may contain Subpaths
|
||||
/// / | \
|
||||
/// Subpath ... Subpath <- Reference from layer paths, one Subpath per layer (for now, will eventually be a CompoundPath)
|
||||
/// / | \
|
||||
/// ManipulatorGroup ... ManipulatorGroup <- Subpath contains many ManipulatorGroups
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ShapeEditor {
|
||||
pub struct SelectedLayerState {
|
||||
selected_points: HashSet<ManipulatorPointId>,
|
||||
}
|
||||
impl SelectedLayerState {
|
||||
pub fn is_selected(&self, point: ManipulatorPointId) -> bool {
|
||||
self.selected_points.contains(&point)
|
||||
}
|
||||
pub fn select_point(&mut self, point: ManipulatorPointId) {
|
||||
self.selected_points.insert(point);
|
||||
}
|
||||
pub fn deselect_point(&mut self, point: ManipulatorPointId) {
|
||||
self.selected_points.remove(&point);
|
||||
}
|
||||
}
|
||||
pub type SelectedShapeState = HashMap<Vec<LayerId>, SelectedLayerState>;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ShapeState {
|
||||
// The layers we can select and edit manipulators (anchors and handles) from
|
||||
selected_layers: Vec<Vec<LayerId>>,
|
||||
pub selected_shape_state: SelectedShapeState,
|
||||
}
|
||||
|
||||
pub struct SelectedPointsInfo<'a> {
|
||||
@@ -36,333 +38,299 @@ pub struct SelectedPointsInfo<'a> {
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ManipulatorPointInfo<'a> {
|
||||
pub shape_layer_path: &'a [LayerId],
|
||||
pub manipulator_group_id: u64,
|
||||
pub manipulator_type: ManipulatorType,
|
||||
pub point_id: ManipulatorPointId,
|
||||
}
|
||||
|
||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeEditor {
|
||||
impl ShapeState {
|
||||
/// Select the first point within the selection threshold.
|
||||
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
|
||||
pub fn select_point(&self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool, responses: &mut VecDeque<Message>) -> Option<SelectedPointsInfo> {
|
||||
if self.selected_layers.is_empty() {
|
||||
pub fn select_point(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option<SelectedPointsInfo> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
|
||||
trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index);
|
||||
if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
|
||||
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
|
||||
|
||||
// If the point we're selecting has already been selected
|
||||
// we can assume this point exists.. since we did just click on it hence the unwrap
|
||||
let is_point_selected = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.editor_state
|
||||
.is_selected;
|
||||
let vector_data = document.layer(&shape_layer_path).ok()?.as_vector_data()?;
|
||||
let manipulator_group = vector_data.manipulator_groups().find(|group| group.id == manipulator_point_id.group)?;
|
||||
let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?;
|
||||
|
||||
let point_position = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.position;
|
||||
|
||||
// The currently selected points (which are then modified to reflect the selection)
|
||||
let mut points = self
|
||||
.selected_layers()
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.flat_map(|(path, shape)| {
|
||||
shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.filter(|(_id, manipulator_group)| manipulator_group.is_anchor_selected())
|
||||
.flat_map(|(id, manipulator_group)| manipulator_group.selected_points().map(move |point| (id, point.manipulator_type)))
|
||||
.map(|(anchor, manipulator_point)| ManipulatorPointInfo {
|
||||
shape_layer_path: path.as_slice(),
|
||||
manipulator_group_id: *anchor,
|
||||
manipulator_type: manipulator_point,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?;
|
||||
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
|
||||
|
||||
// Should we select or deselect the point?
|
||||
let should_select = if is_point_selected { !add_to_selection } else { true };
|
||||
let new_selected = if already_selected { !add_to_selection } else { true };
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
if should_select {
|
||||
// If we're replacing the selection, clear all points in other selected shapes
|
||||
let add = add_to_selection || is_point_selected;
|
||||
if !add {
|
||||
points.clear();
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
if new_selected {
|
||||
let retain_existing_selection = add_to_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
self.deselect_all();
|
||||
}
|
||||
|
||||
// Add to the selected points
|
||||
let point_info = ManipulatorPointInfo {
|
||||
shape_layer_path,
|
||||
manipulator_group_id,
|
||||
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
|
||||
};
|
||||
|
||||
points.push(point_info);
|
||||
responses.push_back(
|
||||
Operation::SelectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![(point_info.manipulator_group_id, point_info.manipulator_type)],
|
||||
add,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
|
||||
selected_shape_state.select_point(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = document
|
||||
.generate_transform_relative_to_viewport(shape_layer_path)
|
||||
.generate_transform_relative_to_viewport(&shape_layer_path)
|
||||
.map(|viewspace| mouse_position - viewspace.transform_point2(point_position))
|
||||
.unwrap_or_default();
|
||||
|
||||
let points = self
|
||||
.selected_shape_state
|
||||
.iter()
|
||||
.flat_map(|(shape_layer_path, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { shape_layer_path, point_id }))
|
||||
.collect();
|
||||
|
||||
return Some(SelectedPointsInfo { points, offset });
|
||||
} else {
|
||||
responses.push_back(
|
||||
Operation::DeselectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
points.retain(|x| {
|
||||
*x != ManipulatorPointInfo {
|
||||
shape_layer_path,
|
||||
manipulator_group_id,
|
||||
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
|
||||
}
|
||||
});
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
|
||||
selected_shape_state.deselect_point(manipulator_point_id);
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Deselect all points if no nearby point
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
self.deselect_all();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// A wrapper for `find_nearest_point_indices()` and returns a [ManipulatorPoint].
|
||||
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a ManipulatorPoint> {
|
||||
let (shape_layer_path, manipulator_group_id, manipulator_point_index) = self.find_nearest_point_indices(document, mouse_position, select_threshold)?;
|
||||
let selected_shape = self.shape(document, shape_layer_path).unwrap();
|
||||
if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) {
|
||||
return manipulator_group.points[manipulator_point_index].as_ref();
|
||||
}
|
||||
None
|
||||
pub fn deselect_all(&mut self) {
|
||||
self.selected_shape_state.values_mut().for_each(|state| state.selected_points.clear());
|
||||
}
|
||||
|
||||
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
|
||||
pub fn set_selected_layers(&mut self, target_layers: Vec<Vec<LayerId>>) {
|
||||
self.selected_layers = target_layers;
|
||||
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
|
||||
for layer in target_layers {
|
||||
self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_layers(&self) -> &Vec<Vec<LayerId>> {
|
||||
&self.selected_layers
|
||||
}
|
||||
|
||||
pub fn selected_layers_ref(&self) -> Vec<&[LayerId]> {
|
||||
self.selected_layers.iter().map(|l| l.as_slice()).collect::<Vec<_>>()
|
||||
pub fn selected_layers(&self) -> impl Iterator<Item = &Vec<LayerId>> {
|
||||
self.selected_shape_state.keys()
|
||||
}
|
||||
|
||||
/// Clear all of the shapes we can modify.
|
||||
pub fn clear_selected_layers(&mut self) {
|
||||
self.selected_layers.clear();
|
||||
self.selected_shape_state.clear();
|
||||
}
|
||||
|
||||
pub fn has_selected_layers(&self) -> bool {
|
||||
!self.selected_layers.is_empty()
|
||||
}
|
||||
|
||||
/// Provide the currently selected manipulators by reference.
|
||||
pub fn selected_manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.selected_manipulator_groups())
|
||||
!self.selected_shape_state.is_empty()
|
||||
}
|
||||
|
||||
/// A mutable iterator of all the manipulators, regardless of selection.
|
||||
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.manipulator_groups().iter())
|
||||
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.iter(document).flat_map(|shape| shape.manipulator_groups())
|
||||
}
|
||||
|
||||
// Sets the selected points to all points for the corresponding intersection
|
||||
pub fn select_all_anchors(&self, responses: &mut VecDeque<Message>, itersections: Vec<u64>) {
|
||||
responses.push_back(Operation::SelectAllAnchors { layer_path: itersections }.into());
|
||||
pub fn select_all_anchors(&mut self, document: &Document, layer_path: &[LayerId]) {
|
||||
let Ok(layer) = document.layer(layer_path) else { return };
|
||||
let Some(vector_data) = layer.as_vector_data() else { return };
|
||||
let Some(state) = self.selected_shape_state.get_mut(layer_path) else { return };
|
||||
for manipulator in vector_data.manipulator_groups() {
|
||||
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor))
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide the currently selected points by reference.
|
||||
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorPoint> {
|
||||
self.selected_manipulator_groups(document).flat_map(|manipulator_group| manipulator_group.selected_points())
|
||||
pub fn selected_points<'a>(&'a self) -> impl Iterator<Item = &'a ManipulatorPointId> {
|
||||
self.selected_shape_state.values().flat_map(|state| &state.selected_points)
|
||||
}
|
||||
|
||||
/// Move the selected points by dragging the mouse.
|
||||
pub fn move_selected_points(&self, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
DocumentMessage::MoveSelectedManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
delta: (delta.x, delta.y),
|
||||
mirror_distance,
|
||||
pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for (layer_path, state) in &self.selected_shape_state {
|
||||
let Ok(layer) = document.layer(&layer_path) else { continue };
|
||||
let Some(vector_data) = layer.as_vector_data() else { continue };
|
||||
|
||||
let transform = document.multiply_transforms(&layer_path).unwrap_or_default();
|
||||
let delta = transform.inverse().transform_vector2(delta);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
if point.manipulator_type.is_handle() && state.is_selected(ManipulatorPointId::new(point.group, SelectedType::Anchor)) {
|
||||
continue;
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue };
|
||||
|
||||
let mut move_point = |point: ManipulatorPointId| {
|
||||
let Some(previous_position) = point.manipulator_type.get_position(group) else { return };
|
||||
let position = previous_position + delta;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
};
|
||||
|
||||
move_point(point);
|
||||
|
||||
if point.manipulator_type == SelectedType::Anchor {
|
||||
move_point(ManipulatorPointId::new(point.group, SelectedType::InHandle));
|
||||
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
|
||||
}
|
||||
|
||||
if mirror_distance && point.manipulator_type != SelectedType::Anchor && vector_data.mirror_angle.contains(&point.group) {
|
||||
let Some(mut origional_handle_position) = point.manipulator_type.get_position(group) else { continue };
|
||||
origional_handle_position += delta;
|
||||
|
||||
let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
|
||||
if state.is_selected(point) {
|
||||
continue;
|
||||
}
|
||||
let position = group.anchor - (origional_handle_position - group.anchor);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The opposing handle lengths.
|
||||
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<u64, f64>> {
|
||||
self.selected_layers()
|
||||
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>> {
|
||||
self.selected_shape_state
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.map(|(path, shape)| {
|
||||
let opposing_handle_lengths = shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.filter_map(|(id, manipulator_group)| {
|
||||
// We will keep track of the opposing handle length when:
|
||||
// i) Both handles exist and exactly one is selected.
|
||||
// ii) The anchor is not selected.
|
||||
// iii) We have to mirror the angle between handles.
|
||||
.filter_map(|(path, state)| {
|
||||
let layer = document.layer(path).ok()?;
|
||||
let vector_data = layer.as_vector_data()?;
|
||||
let opposing_handle_lengths = vector_data
|
||||
.subpaths
|
||||
.iter()
|
||||
.flat_map(|subpath| {
|
||||
subpath.manipulator_groups().iter().filter_map(|manipulator_group| {
|
||||
// We will keep track of the opposing handle length when:
|
||||
// i) Both handles exist and exactly one is selected.
|
||||
// ii) The anchor is not selected.
|
||||
// iii) We have to mirror the angle between handles.
|
||||
|
||||
if !manipulator_group.editor_state.mirror_angle_between_handles {
|
||||
return None;
|
||||
}
|
||||
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
|
||||
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
|
||||
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
|
||||
|
||||
let mut selected_handles = manipulator_group.selected_handles();
|
||||
let handle = selected_handles.next()?;
|
||||
|
||||
// Check that handle is the only selected handle.
|
||||
if selected_handles.next().is_none() {
|
||||
let opposing_handle_position = manipulator_group.opposing_handle(handle)?.position;
|
||||
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
if !anchor.is_selected() {
|
||||
let opposing_handle_length = opposing_handle_position.distance(anchor.position);
|
||||
Some((*id, opposing_handle_length))
|
||||
} else {
|
||||
None
|
||||
if anchor_selected {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
|
||||
(true, false) => SelectedType::InHandle,
|
||||
(false, true) => SelectedType::OutHandle,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let opposing_handle_position = single_selected_handle.opposite().get_position(manipulator_group)?;
|
||||
|
||||
let opposing_handle_length = opposing_handle_position.distance(manipulator_group.anchor);
|
||||
Some((manipulator_group.id, opposing_handle_length))
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
(path.clone(), opposing_handle_lengths)
|
||||
Some((path.clone(), opposing_handle_lengths))
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
}
|
||||
|
||||
/// Reset the opposing handle lengths.
|
||||
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<u64, f64>>, responses: &mut VecDeque<Message>) {
|
||||
self.selected_layers()
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.filter_map(|(path, shape)| opposing_handle_lengths.get(path).map(|layer_opposing_handle_lengths| (path, shape, layer_opposing_handle_lengths)))
|
||||
.flat_map(|(path, shape, layer_opposing_handle_lengths)| {
|
||||
shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.map(move |(id, manipulator_group)| (path, layer_opposing_handle_lengths, id, manipulator_group))
|
||||
})
|
||||
.for_each(|(path, layer_opposing_handle_lengths, id, manipulator_group)| {
|
||||
if !manipulator_group.editor_state.mirror_angle_between_handles {
|
||||
return;
|
||||
}
|
||||
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>, responses: &mut VecDeque<Message>) {
|
||||
for (path, state) in &self.selected_shape_state {
|
||||
let Ok(layer) = document.layer(path) else { continue };
|
||||
let Some(vector_data) = layer.as_vector_data() else { continue };
|
||||
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(path) else { continue };
|
||||
|
||||
let opposing_handle_length = if let Some(length) = layer_opposing_handle_lengths.get(id) {
|
||||
length
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut selected_handles = manipulator_group.selected_handles();
|
||||
let handle = if let Some(handle) = selected_handles.next() {
|
||||
handle
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check that handle is the only selected handle.
|
||||
if selected_handles.next().is_none() {
|
||||
let opposing_handle = if let Some(opposing_handle) = manipulator_group.opposing_handle(handle) {
|
||||
opposing_handle
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let anchor = if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
|
||||
anchor
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
if anchor.is_selected() {
|
||||
return;
|
||||
for subpath in &vector_data.subpaths {
|
||||
for manipulator_group in subpath.manipulator_groups() {
|
||||
if !vector_data.mirror_angle.contains(&manipulator_group.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(offset) = (opposing_handle.position - anchor.position).try_normalize() {
|
||||
let new_opposing_handle_position = anchor.position + offset * (*opposing_handle_length);
|
||||
assert!(new_opposing_handle_position.is_finite(), "Opposing handle not finite!");
|
||||
responses.push_back(
|
||||
Operation::MoveManipulatorPoint {
|
||||
layer_path: path.clone(),
|
||||
id: *id,
|
||||
manipulator_type: opposing_handle.manipulator_type,
|
||||
position: new_opposing_handle_position.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let Some(opposing_handle_length) = opposing_handle_lengths.get(&manipulator_group.id) else { continue };
|
||||
|
||||
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
|
||||
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
|
||||
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
|
||||
|
||||
if anchor_selected {
|
||||
continue;
|
||||
}
|
||||
|
||||
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
|
||||
(true, false) => SelectedType::InHandle,
|
||||
(false, true) => SelectedType::OutHandle,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let Some(opposing_handle) = single_selected_handle.opposite().get_position(manipulator_group) else { continue };
|
||||
|
||||
let Some(offset) = (opposing_handle - manipulator_group.anchor).try_normalize() else { continue };
|
||||
|
||||
let point = ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite());
|
||||
let position = manipulator_group.anchor + offset * (*opposing_handle_length);
|
||||
assert!(position.is_finite(), "Opposing handle not finite!");
|
||||
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dissolve the selected points.
|
||||
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());
|
||||
}
|
||||
|
||||
/// Toggle if the handles should mirror angle across the anchor position.
|
||||
pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
DocumentMessage::ToggleSelectedHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
toggle_angle,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
for (layer, state) in &self.selected_shape_state {
|
||||
for &point in &state.selected_points {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorPoint { point },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deselect all manipulators from the shapes that the manipulation handler has created.
|
||||
pub fn deselect_all_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
/// Toggle if the handles should mirror angle across the anchor position.
|
||||
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
|
||||
for (layer, state) in &self.selected_shape_state {
|
||||
for point in &state.selected_points {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the shapes.
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a Subpath> + 'a {
|
||||
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_subpath())
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorData> + 'a {
|
||||
self.selected_shape_state
|
||||
.keys()
|
||||
.flat_map(|layer_id| document.layer(layer_id))
|
||||
.filter_map(|shape| shape.as_vector_data())
|
||||
}
|
||||
|
||||
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
|
||||
/// Return value is an `Option` of the tuple representing `(layer path, ManipulatorGroup ID, ManipulatorType enum index)`.
|
||||
fn find_nearest_point_indices(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
|
||||
if self.selected_layers.is_empty() {
|
||||
pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec<LayerId>, ManipulatorPointId)> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let select_threshold_squared = select_threshold * select_threshold;
|
||||
// Find the closest control point among all elements of shapes_to_modify
|
||||
for layer in self.selected_layers.iter() {
|
||||
if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
|
||||
for layer in self.selected_shape_state.keys() {
|
||||
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) {
|
||||
// Choose the first point under the threshold
|
||||
if distance_squared < select_threshold_squared {
|
||||
trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index);
|
||||
return Some((layer, manipulator_id, manipulator_point_index));
|
||||
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
|
||||
return Some((layer.clone(), manipulator_point_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,51 +341,54 @@ impl ShapeEditor {
|
||||
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
|
||||
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
|
||||
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
|
||||
/// Return value is an `Option` of the tuple representing `(manipulator ID, manipulator point index, distance squared)`.
|
||||
fn closest_point_in_layer(&self, document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(u64, usize, f64)> {
|
||||
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
|
||||
let mut result: Option<(u64, usize, f64)> = None;
|
||||
/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.
|
||||
fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
|
||||
let mut closest_distance_squared: f64 = f64::MAX;
|
||||
let mut result = None;
|
||||
|
||||
if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() {
|
||||
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_point_index = manipulator.closest_point(&viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
|
||||
if let Some(point) = &manipulator.points[manipulator_point_index] {
|
||||
if point.editor_state.can_be_selected {
|
||||
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((*manipulator_id, manipulator_point_index, distance_squared));
|
||||
}
|
||||
}
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
for subpath in &vector_data.subpaths {
|
||||
for manipulator in subpath.manipulator_groups() {
|
||||
let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
///
|
||||
/// Returns a tuple of [`BezierId`] and `t` as an f64.
|
||||
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(BezierId, f64)> {
|
||||
/// Returns a tuple of subpath_index, manipulator_start and `t` as an f64.
|
||||
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> {
|
||||
let transform = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() };
|
||||
|
||||
let mut result: Option<(BezierId, f64)> = None;
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared: f64 = tolerance * tolerance;
|
||||
|
||||
for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() {
|
||||
let bezier = bezier_id.internal;
|
||||
let t = bezier.project(layer_pos, projection_options);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
for subpath in &vector_data.subpaths {
|
||||
for (manipulator_index, bezier) in subpath.iter().enumerate() {
|
||||
let t = bezier.project(layer_pos, projection_options);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((bezier_id, t));
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
let start = subpath.manipulator_groups()[manipulator_index];
|
||||
let end = subpath.manipulator_groups()[(manipulator_index + 1) % subpath.len()];
|
||||
result = Some((start.id, end.id, bezier, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,34 +397,36 @@ impl ShapeEditor {
|
||||
|
||||
/// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool).
|
||||
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) {
|
||||
let [first, second] = bezier_id.internal.split(TValue::Parametric(t));
|
||||
for layer_path in self.selected_layers() {
|
||||
if let Some((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) {
|
||||
let [first, second] = bezier.split(TValue::Parametric(t));
|
||||
|
||||
// Adjust the first manipulator group's out handle
|
||||
let out_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
id: bezier_id.start,
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: first.handle_start().map(|p| p.into()),
|
||||
let point = ManipulatorPointId::new(start, SelectedType::OutHandle);
|
||||
let position = first.handle_start().unwrap_or(first.start());
|
||||
let out_handle = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
};
|
||||
responses.add(out_handle);
|
||||
|
||||
// Insert a new manipulator group between the existing ones
|
||||
let insert = Operation::InsertManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group: ManipulatorGroup::new_with_handles(first.end(), first.handle_end(), second.handle_start()),
|
||||
after_id: bezier_id.end,
|
||||
let manipulator_group = bezier_rs::ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start());
|
||||
let insert = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start },
|
||||
};
|
||||
responses.add(insert);
|
||||
|
||||
// Adjust the last manipulator group's in handle
|
||||
let in_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
id: bezier_id.end,
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
position: second.handle_end().map(|p| p.into()),
|
||||
let point = ManipulatorPointId::new(end, SelectedType::InHandle);
|
||||
let position = second.handle_end().unwrap_or(second.end());
|
||||
let in_handle = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
};
|
||||
responses.add(in_handle);
|
||||
|
||||
responses.extend([out_handle.into(), insert.into(), in_handle.into()]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -462,53 +435,57 @@ impl ShapeEditor {
|
||||
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
|
||||
pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
||||
let mut process_layer = |layer_path| {
|
||||
let manipulator_groups = document.layer(layer_path).ok()?.as_subpath()?.manipulator_groups();
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
|
||||
let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared = tolerance * tolerance;
|
||||
|
||||
// Find the closest anchor point on the current layer
|
||||
for (index, (&bezier_id, group)) in manipulator_groups.enumerate().enumerate() {
|
||||
if let Some(anchor) = &group.points[ManipulatorType::Anchor as usize] {
|
||||
let screenspace = transform_to_screenspace.transform_point2(anchor.position);
|
||||
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
|
||||
for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() {
|
||||
let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((anchor.position, index, bezier_id, group));
|
||||
result = Some((subpath_index, manipulator_index, manipulator));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (anchor_position, index, bezier_id, group) = result?;
|
||||
let (subpath_index, index, manipulator) = result?;
|
||||
let anchor_position = manipulator.anchor;
|
||||
|
||||
let subpath = &vector_data.subpaths[subpath_index];
|
||||
|
||||
// Check by comparing the handle positions to the anchor if this maniuplator group is a point
|
||||
let already_sharp = match &group.points {
|
||||
[_, Some(in_handle), Some(out_handle)] => anchor_position.abs_diff_eq(in_handle.position, f64::EPSILON * 100.) && anchor_position.abs_diff_eq(out_handle.position, f64::EPSILON * 100.),
|
||||
[_, Some(handle), None] | [_, None, Some(handle)] => anchor_position.abs_diff_eq(handle.position, f64::EPSILON * 100.),
|
||||
[_, None, None] => true,
|
||||
let already_sharp = match (manipulator.in_handle, manipulator.out_handle) {
|
||||
(Some(in_handle), Some(out_handle)) => anchor_position.abs_diff_eq(in_handle, 1e-10) && anchor_position.abs_diff_eq(out_handle, 1e-10),
|
||||
(Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10),
|
||||
(None, None) => true,
|
||||
};
|
||||
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let (in_handle, out_handle) = if already_sharp {
|
||||
let is_closed = manipulator_groups.last().filter(|group| group.is_close()).is_some();
|
||||
let is_closed = subpath.closed();
|
||||
|
||||
// Grab the next and previous manipulator groups by simply looking at the next / previous index
|
||||
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.by_index(index)).and_then(|group| group.points[0].as_ref());
|
||||
let mut next_position = manipulator_groups.by_index(index + 1).and_then(|group| group.points[0].as_ref());
|
||||
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.get(index)).map(|group| group.anchor);
|
||||
let mut next_position = manipulator_groups.get(index + 1).map(|group| group.anchor);
|
||||
|
||||
// Wrapping around closed path (assuming format is point elements then a single close path)
|
||||
// Wrapping around closed path
|
||||
if is_closed {
|
||||
previous_position = previous_position.or_else(|| manipulator_groups.iter().nth_back(1).and_then(|group| group.points[0].as_ref()));
|
||||
next_position = next_position.or_else(|| manipulator_groups.first().and_then(|group| group.points[0].as_ref()));
|
||||
previous_position = previous_position.or_else(|| manipulator_groups.last().map(|group| group.anchor));
|
||||
next_position = next_position.or_else(|| manipulator_groups.first().map(|group| group.anchor));
|
||||
}
|
||||
|
||||
// To find the length of the new tangent we just take the distance to the anchor and divide by 3 (pretty arbitrary)
|
||||
let length_previous = previous_position.map(|point| (point.position - anchor_position).length() / 3.);
|
||||
let length_next = next_position.map(|point| (point.position - anchor_position).length() / 3.);
|
||||
let length_previous = previous_position.map(|point| (point - anchor_position).length() / 3.);
|
||||
let length_next = next_position.map(|point| (point - anchor_position).length() / 3.);
|
||||
|
||||
// Use the position relative to the anchor
|
||||
let previous_angle = previous_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let next_angle = next_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let previous_angle = previous_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let next_angle = next_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
|
||||
// The direction of the handles is either the perpendicular vector to the sum of the anchors' positions or just the anchor's position (if only one)
|
||||
let handle_direction = match (previous_angle, next_angle) {
|
||||
@@ -519,21 +496,20 @@ impl ShapeEditor {
|
||||
};
|
||||
|
||||
// Mirror the angle but not the distance
|
||||
responses.push_back(
|
||||
Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: manipulator.id,
|
||||
mirror_angle: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
let (sin, cos) = handle_direction.sin_cos();
|
||||
let mut handle_vector = DVec2::new(cos, sin);
|
||||
|
||||
// Flip the vector if it is not facing towards the same direction as the anchor
|
||||
if previous_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|
||||
|| next_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
|
||||
if previous_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|
||||
|| next_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
|
||||
{
|
||||
handle_vector = -handle_vector;
|
||||
}
|
||||
@@ -548,34 +524,26 @@ impl ShapeEditor {
|
||||
|
||||
// Push both in and out handles into the correct position
|
||||
if let Some(in_handle) = in_handle {
|
||||
let in_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
position: Some(in_handle.into()),
|
||||
};
|
||||
responses.push_back(in_handle.into());
|
||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
|
||||
});
|
||||
}
|
||||
if let Some(out_handle) = out_handle {
|
||||
let out_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: Some(out_handle.into()),
|
||||
};
|
||||
responses.push_back(out_handle.into());
|
||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle },
|
||||
});
|
||||
}
|
||||
Some(true)
|
||||
};
|
||||
for layer_path in &self.selected_layers {
|
||||
for layer_path in self.selected_shape_state.keys() {
|
||||
if let Some(result) = process_layer(layer_path) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
|
||||
document.layer(layer_id).ok()?.as_subpath()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ use crate::consts::{
|
||||
};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType};
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
use document_legacy::layers::style::{self, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::f64::consts::PI;
|
||||
@@ -260,35 +260,26 @@ impl SnapManager {
|
||||
include_handles: bool,
|
||||
ignore_points: &[ManipulatorPointInfo],
|
||||
) {
|
||||
if let LayerDataType::Shape(shape_layer) = &layer.data {
|
||||
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
|
||||
let snap_points = shape_layer
|
||||
.shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.flat_map(|(id, shape)| {
|
||||
if include_handles {
|
||||
[
|
||||
(*id, &shape.points[ManipulatorType::Anchor]),
|
||||
(*id, &shape.points[ManipulatorType::InHandle]),
|
||||
(*id, &shape.points[ManipulatorType::OutHandle]),
|
||||
]
|
||||
} else {
|
||||
[(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)]
|
||||
}
|
||||
})
|
||||
.filter_map(|(id, point)| point.as_ref().map(|val| (id, val)))
|
||||
.filter(|(id, point)| {
|
||||
!ignore_points.contains(&ManipulatorPointInfo {
|
||||
shape_layer_path: path,
|
||||
manipulator_group_id: *id,
|
||||
manipulator_type: point.manipulator_type,
|
||||
})
|
||||
})
|
||||
.map(|(_id, point)| DVec2::new(point.position.x, point.position.y))
|
||||
.map(|pos| transform.transform_point2(pos));
|
||||
self.add_snap_points(document_message_handler, input, snap_points);
|
||||
}
|
||||
let Some(vector_data) = &layer.as_vector_data() else { return };
|
||||
|
||||
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
|
||||
let snap_points = vector_data
|
||||
.manipulator_groups()
|
||||
.flat_map(|group| {
|
||||
if include_handles {
|
||||
[
|
||||
Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)),
|
||||
group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)),
|
||||
group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)),
|
||||
]
|
||||
} else {
|
||||
[Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None]
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id }))
|
||||
.map(|(_, pos)| transform.transform_point2(pos));
|
||||
self.add_snap_points(document_message_handler, input, snap_points);
|
||||
}
|
||||
|
||||
/// Adds all of the shape handles in the document, including bézier handles of the points specified
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::utility_types::{tool_message_to_tool_type, ToolFsmState};
|
||||
use super::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
use super::common_functionality::shape_editor::ShapeState;
|
||||
use super::utility_types::{tool_message_to_tool_type, ToolActionHandlerData, ToolFsmState};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
@@ -11,8 +13,10 @@ use graphene_core::raster::color::Color;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ToolMessageHandler {
|
||||
tool_state: ToolFsmState,
|
||||
transform_layer_handler: TransformLayerMessageHandler,
|
||||
pub tool_state: ToolFsmState,
|
||||
pub transform_layer_handler: TransformLayerMessageHandler,
|
||||
pub shape_overlay: OverlayRenderer,
|
||||
pub shape_editor: ShapeState,
|
||||
}
|
||||
|
||||
impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocessorMessageHandler, &PersistentData)> for ToolMessageHandler {
|
||||
@@ -31,7 +35,7 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||
#[remain::unsorted]
|
||||
ToolMessage::TransformLayer(message) => self
|
||||
.transform_layer_handler
|
||||
.process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data)),
|
||||
.process_message(message, responses, (document, input, &render_data, &self.tool_state.tool_data, &mut self.shape_editor)),
|
||||
|
||||
#[remain::unsorted]
|
||||
ToolMessage::ActivateToolSelect => responses.push_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }.into()),
|
||||
@@ -70,7 +74,6 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||
|
||||
ToolMessage::ActivateTool { tool_type } => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
let old_tool = tool_data.active_tool_type;
|
||||
|
||||
// Do nothing if switching to the same tool
|
||||
@@ -81,13 +84,26 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||
// Send the old and new tools a transition to their FSM Abort states
|
||||
let mut send_abort_to_tool = |tool_type, update_hints_and_cursor: bool| {
|
||||
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
|
||||
let mut data = ToolActionHandlerData {
|
||||
document,
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
render_data: &render_data,
|
||||
shape_overlay: &mut self.shape_overlay,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
};
|
||||
if let Some(tool_abort_message) = tool.event_to_message_map().tool_abort {
|
||||
tool.process_message(tool_abort_message, responses, (document, document_id, document_data, input, &render_data));
|
||||
tool.process_message(tool_abort_message, responses, &mut data);
|
||||
}
|
||||
|
||||
if update_hints_and_cursor {
|
||||
tool.process_message(ToolMessage::UpdateHints, responses, (document, document_id, document_data, input, &render_data));
|
||||
tool.process_message(ToolMessage::UpdateCursor, responses, (document, document_id, document_data, input, &render_data));
|
||||
if self.transform_layer_handler.is_transforming() {
|
||||
self.transform_layer_handler.hints(responses);
|
||||
} else {
|
||||
tool.process_message(ToolMessage::UpdateHints, responses, &mut data)
|
||||
}
|
||||
tool.process_message(ToolMessage::UpdateCursor, responses, &mut data);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -150,13 +166,19 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||
document_data.update_working_colors(responses);
|
||||
responses.push_back(FrontendMessage::TriggerRefreshBoundsOfViewports.into());
|
||||
|
||||
let mut data = ToolActionHandlerData {
|
||||
document,
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
render_data: &render_data,
|
||||
shape_overlay: &mut self.shape_overlay,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
};
|
||||
|
||||
// Set initial hints and cursor
|
||||
tool_data
|
||||
.active_tool_mut()
|
||||
.process_message(ToolMessage::UpdateHints, responses, (document, document_id, document_data, input, &render_data));
|
||||
tool_data
|
||||
.active_tool_mut()
|
||||
.process_message(ToolMessage::UpdateCursor, responses, (document, document_id, document_data, input, &render_data));
|
||||
tool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data);
|
||||
tool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data);
|
||||
}
|
||||
ToolMessage::RefreshToolOptions => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
@@ -210,12 +232,28 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, u64, &InputPreprocess
|
||||
ToolMessage::UpdateCursor | ToolMessage::UpdateHints => self.tool_state.tool_data.active_tool_type,
|
||||
tool_message => tool_message_to_tool_type(tool_message),
|
||||
};
|
||||
let document_data = &self.tool_state.document_tool_data;
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
||||
if let Some(tool) = tool_data.tools.get_mut(&tool_type) {
|
||||
if tool_type == tool_data.active_tool_type {
|
||||
tool.process_message(tool_message, responses, (document, document_id, document_data, input, &render_data));
|
||||
let mut data = ToolActionHandlerData {
|
||||
document,
|
||||
document_id,
|
||||
global_tool_data: &self.tool_state.document_tool_data,
|
||||
input,
|
||||
render_data: &render_data,
|
||||
shape_overlay: &mut self.shape_overlay,
|
||||
shape_editor: &mut self.shape_editor,
|
||||
};
|
||||
if matches!(tool_message, ToolMessage::UpdateHints) {
|
||||
if self.transform_layer_handler.is_transforming() {
|
||||
self.transform_layer_handler.hints(responses);
|
||||
} else {
|
||||
tool.process_message(ToolMessage::UpdateHints, responses, &mut data)
|
||||
}
|
||||
} else {
|
||||
tool.process_message(tool_message, responses, &mut data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ impl ToolMetadata for ArtboardTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ impl Fsm for ArtboardToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Fill;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -49,8 +50,8 @@ impl ToolMetadata for EllipseTool {
|
||||
|
||||
impl PropertyHolder for EllipseTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EllipseTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EllipseTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +101,13 @@ impl Fsm for EllipseToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -114,18 +121,32 @@ impl Fsm for EllipseToolFsmState {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddEllipse {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
// Create a new layer path for this shape
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
|
||||
// Create a new ellipse vector shape
|
||||
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
||||
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
// Set the four manipulator groups to have their handle angles mirrored by default
|
||||
for manipulator_group in manipulator_groups {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: manipulator_group.id,
|
||||
mirror_angle: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Set the fill color to the primary working color
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ impl ToolMetadata for EyedropperTool {
|
||||
|
||||
impl PropertyHolder for EyedropperTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl Fsm for EyedropperToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
(_document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { global_tool_data, input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::style::Fill;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -46,8 +45,8 @@ impl ToolMetadata for FillTool {
|
||||
|
||||
impl PropertyHolder for FillTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -84,7 +83,13 @@ impl Fsm for FillToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
_tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -106,9 +111,12 @@ impl Fsm for FillToolFsmState {
|
||||
};
|
||||
let fill = Fill::Solid(color);
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
responses.push_back(Operation::SetLayerFill { path: path.to_vec(), fill }.into());
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
responses.add(DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: vec![path.to_vec()],
|
||||
});
|
||||
responses.add(GraphOperationMessage::FillSet { layer: path.to_vec(), fill });
|
||||
responses.add(DocumentMessage::CommitTransaction);
|
||||
}
|
||||
|
||||
Ready
|
||||
|
||||
@@ -3,14 +3,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::MouseMotion;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -82,8 +83,8 @@ impl PropertyHolder for FreehandTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
FreehandToolMessageOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -137,7 +138,9 @@ impl Fsm for FreehandToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document, global_tool_data, input, ..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -159,7 +162,7 @@ impl Fsm for FreehandToolFsmState {
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -170,15 +173,14 @@ impl Fsm for FreehandToolFsmState {
|
||||
tool_data.points.push(pos);
|
||||
}
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, DragStop) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_polyline(tool_data, global_tool_data));
|
||||
add_polyline(tool_data, global_tool_data, responses);
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
@@ -214,15 +216,13 @@ fn remove_preview(data: &FreehandToolData) -> Message {
|
||||
Operation::DeleteLayer { path: data.path.clone().unwrap() }.into()
|
||||
}
|
||||
|
||||
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData) -> Message {
|
||||
let points: Vec<(f64, f64)> = data.points.iter().map(|p| (p.x, p.y)).collect();
|
||||
fn add_polyline(data: &FreehandToolData, tool_data: &DocumentToolData, responses: &mut VecDeque<Message>) {
|
||||
let layer_path = data.path.clone().unwrap();
|
||||
let subpath = bezier_rs::Subpath::from_anchors(data.points.iter().copied(), false);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
|
||||
Operation::AddPolyline {
|
||||
path: data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(tool_data.primary_color, data.weight)), style::Fill::None),
|
||||
}
|
||||
.into()
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path,
|
||||
stroke: Stroke::new(tool_data.primary_color, data.weight),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ impl ToolMetadata for GradientTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
GradientOptionsUpdate::Type(gradient_type) => {
|
||||
@@ -353,8 +353,8 @@ impl SelectedGradient {
|
||||
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.gradient.transform = self.transform;
|
||||
let fill = Fill::Gradient(self.gradient.clone());
|
||||
let path = self.path.clone();
|
||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||
let layer = self.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +396,13 @@ impl Fsm for GradientToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -450,8 +456,8 @@ impl Fsm for GradientToolFsmState {
|
||||
// The gradient has only one point and so should become a fill
|
||||
if selected_gradient.gradient.positions.len() == 1 {
|
||||
let fill = Fill::Solid(selected_gradient.gradient.positions[0].1.unwrap_or(Color::BLACK));
|
||||
let path = selected_gradient.path.clone();
|
||||
responses.push_back(Operation::SetLayerFill { path, fill }.into());
|
||||
let layer = selected_gradient.path.clone();
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ pub enum ImaginateToolMessage {
|
||||
|
||||
impl PropertyHolder for ImaginateTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ImaginateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ImaginateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Fsm for ImaginateToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -5,15 +5,15 @@ use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -82,8 +82,8 @@ impl PropertyHolder for LineTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for LineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for LineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, messages: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Line(LineToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
LineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -137,7 +137,13 @@ impl Fsm for LineToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -151,22 +157,19 @@ impl Fsm for LineToolFsmState {
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
|
||||
tool_data.drag_start = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
|
||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
||||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
tool_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_options.line_weight),
|
||||
});
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddLine {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Redraw { center, snap_angle, lock_angle }) => {
|
||||
@@ -249,9 +252,10 @@ fn generate_transform(tool_data: &mut LineToolData, lock_angle: bool, snap_angle
|
||||
line_length *= 2.;
|
||||
}
|
||||
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start).to_cols_array(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.path.clone().unwrap(),
|
||||
transform: glam::DAffine2::from_scale_angle_translation(DVec2::new(line_length, 1.), angle, start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ impl ToolMetadata for NavigateTool {
|
||||
|
||||
impl PropertyHolder for NavigateTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ impl Fsm for NavigateToolFsmState {
|
||||
self,
|
||||
message: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(_document, _document_id, _global_tool_data, input, _render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
messages: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -37,8 +37,8 @@ pub enum NodeGraphFrameToolMessage {
|
||||
|
||||
impl PropertyHolder for NodeGraphFrameTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for NodeGraphFrameTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NodeGraphFrameTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl Fsm for NodeGraphToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
|
||||
@@ -4,13 +4,14 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeEditor};
|
||||
use crate::messages::tool::common_functionality::shape_editor::{ManipulatorPointInfo, ShapeState};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, HintData, HintGroup, HintInfo, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::LayerId;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -62,8 +63,8 @@ impl ToolMetadata for PathTool {
|
||||
|
||||
impl PropertyHolder for PathTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -106,14 +107,28 @@ enum PathToolFsmState {
|
||||
|
||||
#[derive(Default)]
|
||||
struct PathToolData {
|
||||
shape_editor: ShapeEditor,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
snap_manager: SnapManager,
|
||||
|
||||
drag_start_pos: DVec2,
|
||||
previous_mouse_position: DVec2,
|
||||
alt_debounce: bool,
|
||||
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<u64, f64>>>,
|
||||
opposing_handle_lengths: Option<HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
fn refresh_overlays(&mut self, document: &DocumentMessageHandler, shape_editor: &mut ShapeState, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Render the new overlays
|
||||
for layer_path in shape_editor.selected_shape_state.keys() {
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self.opposing_handle_lengths = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PathToolFsmState {
|
||||
@@ -124,27 +139,25 @@ impl Fsm for PathToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
input,
|
||||
render_data,
|
||||
shape_editor,
|
||||
shape_overlay,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
if let ToolMessage::Path(event) = event {
|
||||
match (self, event) {
|
||||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Set the newly targeted layers to visible
|
||||
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
|
||||
tool_data.shape_editor.set_selected_layers(layer_paths);
|
||||
// Render the new overlays
|
||||
for layer_path in tool_data.shape_editor.selected_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
shape_editor.set_selected_layers(layer_paths);
|
||||
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
// This can happen in any state (which is why we return self)
|
||||
self
|
||||
}
|
||||
@@ -152,7 +165,7 @@ impl Fsm for PathToolFsmState {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -162,39 +175,39 @@ impl Fsm for PathToolFsmState {
|
||||
let shift_pressed = input.keyboard.get(add_to_selection as usize);
|
||||
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
let selected_layers = shape_editor.selected_layers().cloned().collect();
|
||||
|
||||
// Select the first point within the threshold (in pixels)
|
||||
if let Some(mut selected_points) = tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed, responses)
|
||||
{
|
||||
if let Some(mut selected_points) = shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, shift_pressed) {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let ignore_document = tool_data.shape_editor.selected_layers().clone();
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&ignore_document), None, render_data), true, true);
|
||||
.start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, render_data), true, true);
|
||||
|
||||
// Do not snap against handles when anchor is selected
|
||||
let mut extension = Vec::new();
|
||||
let mut additional_selected_points = Vec::new();
|
||||
for point in selected_points.points.iter() {
|
||||
if point.manipulator_type == ManipulatorType::Anchor {
|
||||
extension.push(ManipulatorPointInfo {
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
..*point
|
||||
if point.point_id.manipulator_type == SelectedType::Anchor {
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
shape_layer_path: point.shape_layer_path,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
|
||||
});
|
||||
extension.push(ManipulatorPointInfo {
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
..*point
|
||||
additional_selected_points.push(ManipulatorPointInfo {
|
||||
shape_layer_path: point.shape_layer_path,
|
||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
|
||||
});
|
||||
}
|
||||
}
|
||||
selected_points.points.extend(extension);
|
||||
selected_points.points.extend(additional_selected_points);
|
||||
|
||||
let include_handles = tool_data.shape_editor.selected_layers_ref();
|
||||
let include_handles: Vec<_> = selected_layers.iter().map(|x| x.as_slice()).collect();
|
||||
tool_data.snap_manager.add_all_document_handles(document, input, &include_handles, &[], &selected_points.points);
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position - selected_points.offset;
|
||||
|
||||
tool_data.refresh_overlays(document, shape_editor, shape_overlay, responses);
|
||||
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// We didn't find a point nearby, so consider selecting the nearest shape instead
|
||||
@@ -219,7 +232,7 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.drag_start_pos = input.mouse.position;
|
||||
tool_data.previous_mouse_position = input.mouse.position;
|
||||
// Selects all the anchor points when clicking in a filled area of shape. If two shapes intersect we pick the topmost layer.
|
||||
tool_data.shape_editor.select_all_anchors(responses, top_most_intersection);
|
||||
shape_editor.select_all_anchors(&document.document_legacy, &top_most_intersection);
|
||||
return PathToolFsmState::Dragging;
|
||||
}
|
||||
} else {
|
||||
@@ -241,51 +254,47 @@ impl Fsm for PathToolFsmState {
|
||||
) => {
|
||||
// Determine when alt state changes
|
||||
let alt_pressed = input.keyboard.get(alt_mirror_angle as usize);
|
||||
if alt_pressed != tool_data.alt_debounce {
|
||||
tool_data.alt_debounce = alt_pressed;
|
||||
// Only on alt down
|
||||
if alt_pressed {
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
tool_data.shape_editor.toggle_handle_mirroring_on_selected(true, responses);
|
||||
}
|
||||
|
||||
// Only on alt down
|
||||
if alt_pressed && !tool_data.alt_debounce {
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
shape_editor.toggle_handle_mirroring_on_selected(responses);
|
||||
}
|
||||
tool_data.alt_debounce = alt_pressed;
|
||||
|
||||
// Determine when shift state changes
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if shift_pressed {
|
||||
if tool_data.opposing_handle_lengths.is_none() {
|
||||
tool_data.opposing_handle_lengths = Some(tool_data.shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
tool_data.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.document_legacy));
|
||||
}
|
||||
} else {
|
||||
if let Some(opposing_handle_lengths) = &tool_data.opposing_handle_lengths {
|
||||
tool_data.shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
shape_editor.reset_opposing_handle_lengths(&document.document_legacy, opposing_handle_lengths, responses);
|
||||
tool_data.opposing_handle_lengths = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Move the selected points by the mouse position
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
tool_data
|
||||
.shape_editor
|
||||
.move_selected_points(snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
|
||||
shape_editor.move_selected_points(&document.document_legacy, snapped_position - tool_data.previous_mouse_position, shift_pressed, responses);
|
||||
tool_data.previous_mouse_position = snapped_position;
|
||||
PathToolFsmState::Dragging
|
||||
}
|
||||
// Mouse up
|
||||
(_, PathToolMessage::DragStop { shift_mirror_distance }) => {
|
||||
let selected_points = tool_data.shape_editor.selected_points(&document.document_legacy);
|
||||
let nearest_point = tool_data.shape_editor.find_nearest_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD);
|
||||
let nearest_point = shape_editor
|
||||
.find_nearest_point_indices(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD)
|
||||
.map(|(_, nearest_point)| nearest_point)
|
||||
.clone();
|
||||
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
|
||||
for point in selected_points {
|
||||
if nearest_point == Some(point) {
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
tool_data
|
||||
.shape_editor
|
||||
.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false, responses);
|
||||
}
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
||||
if clicked_selected {
|
||||
shape_editor.deselect_all();
|
||||
shape_editor.select_point(&document.document_legacy, input.mouse.position, SELECTION_THRESHOLD, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,18 +305,18 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
tool_data.shape_editor.delete_selected_points(responses);
|
||||
shape_editor.delete_selected_points(responses);
|
||||
responses.push_back(PathToolMessage::SelectionChanged.into());
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::InsertPoint) => {
|
||||
// First we try and flip the sharpness (if they have clicked on an anchor)
|
||||
if !tool_data.shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
if !shape_editor.flip_sharp(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses) {
|
||||
// If not, then we try and split the path that may have been clicked upon
|
||||
tool_data.shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
shape_editor.split(&document.document_legacy, input.mouse.position, SELECTION_TOLERANCE, responses);
|
||||
}
|
||||
|
||||
self
|
||||
@@ -315,7 +324,7 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Abort) => {
|
||||
// TODO Tell overlay manager to remove the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
||||
@@ -3,17 +3,20 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::Stroke;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -98,8 +101,8 @@ impl PropertyHolder for PenTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -139,18 +142,342 @@ impl ToolTransition for PenTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ModifierState {
|
||||
snap_angle: bool,
|
||||
lock_angle: bool,
|
||||
break_handle: bool,
|
||||
}
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct PenToolData {
|
||||
weight: f64,
|
||||
path: Option<Vec<LayerId>>,
|
||||
overlay_renderer: OverlayRenderer,
|
||||
subpath_index: usize,
|
||||
snap_manager: SnapManager,
|
||||
should_mirror: bool,
|
||||
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
||||
from_start: bool,
|
||||
angle: f64,
|
||||
}
|
||||
impl PenToolData {
|
||||
fn extend_subpath(&mut self, layer: &[LayerId], subpath_index: usize, from_start: bool, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.path = Some(layer.to_vec());
|
||||
self.from_start = from_start;
|
||||
self.subpath_index = subpath_index;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let Some(vector_data) = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_vector_data()) else { return };
|
||||
let manipulator_groups = vector_data.subpaths[subpath_index].manipulator_groups();
|
||||
let Some(last_handle) = (if from_start { manipulator_groups.first() } else { manipulator_groups.last() }) else { return };
|
||||
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: last_handle.id,
|
||||
mirror_angle: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
fn create_new_path(&mut self, document: &DocumentMessageHandler, line_weight: f64, color: Color, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
// Deselect layers because we are now creating a new layer
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = document.document_legacy.multiply_transforms(&layer_path[..layer_path.len() - 1]).unwrap_or_default();
|
||||
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
self.weight = line_weight;
|
||||
|
||||
// Create the initial shape with a `bez_path` (only contains a moveto initially)
|
||||
let subpath = bezier_rs::Subpath::new(vec![bezier_rs::ManipulatorGroup::new(start_position, Some(start_position), Some(start_position))], false);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(color, line_weight),
|
||||
});
|
||||
|
||||
self.path = Some(layer_path);
|
||||
self.from_start = false;
|
||||
self.subpath_index = 0;
|
||||
}
|
||||
/// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
///
|
||||
/// TODO: tooltip / user documentation?
|
||||
fn check_break(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<()> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let previous_anchor = previous_manipulator_group.anchor;
|
||||
|
||||
// Break the control
|
||||
let on_top = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if !on_top {
|
||||
return None;
|
||||
}
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let point = ManipulatorPointId::new(previous_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
||||
});
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let id = previous_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
});
|
||||
|
||||
// The overlay system cannot detect deleted points so we must just delete all the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
self.should_mirror = false;
|
||||
None
|
||||
}
|
||||
|
||||
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, shape_overlay: &mut OverlayRenderer, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let first_manipulator_group = if self.from_start {
|
||||
subpath.manipulator_groups().last()?
|
||||
} else {
|
||||
subpath.manipulator_groups().first()?
|
||||
};
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
let last_in = inwards_handle.get_position(last_manipulator_group)?;
|
||||
|
||||
let transformed_distance_between_squared = transform.transform_point2(last_anchor).distance_squared(transform.transform_point2(first_anchor));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
let should_close_path = transformed_distance_between_squared < snap_point_tolerance_squared && previous_manipulator_group.is_some();
|
||||
if should_close_path {
|
||||
// Move the in handle of the first point to where the user has placed it
|
||||
let point = ManipulatorPointId::new(first_manipulator_group.id, inwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
||||
});
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let id = first_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: false },
|
||||
});
|
||||
|
||||
// Remove the point that has just been placed
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
|
||||
// Push a close path node
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetClosed { index: 0, closed: true },
|
||||
});
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
// Clean up tool data
|
||||
self.path = None;
|
||||
self.snap_manager.cleanup(responses);
|
||||
|
||||
// Return to ready state
|
||||
return Some(PenToolFsmState::Ready);
|
||||
}
|
||||
// Add a new manipulator for the next anchor that we will place
|
||||
if let Some(out_handle) = outwards_handle.get_position(last_manipulator_group) {
|
||||
responses.push_back(add_manipulator_group(&self.path, self.from_start, bezier_rs::ManipulatorGroup::new_anchor(out_handle)));
|
||||
}
|
||||
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
fn drag_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.first()? } else { manipulator_groups.last()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, last_anchor);
|
||||
|
||||
// Update points on current segment (to show preview of new handle)
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
|
||||
let should_mirror = !modifiers.break_handle && self.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor - (pos - last_anchor);
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
let id = last_manipulator_group.id;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring { id, mirror_angle: should_mirror },
|
||||
});
|
||||
|
||||
Some(PenToolFsmState::DraggingHandle)
|
||||
}
|
||||
|
||||
fn place_anchor(&mut self, document: &DocumentMessageHandler, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let first_manipulator_group = if self.from_start { manipulator_groups.last()? } else { manipulator_groups.first()? };
|
||||
|
||||
// Get manipulator points
|
||||
let first_anchor = first_manipulator_group.anchor;
|
||||
|
||||
let mouse = self.snap_manager.snap_position(responses, document, mouse);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
// Snap to the first point (to show close path)
|
||||
let show_close_path = mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if show_close_path {
|
||||
pos = first_anchor;
|
||||
}
|
||||
|
||||
if let Some(relative_previous_anchor) = previous_manipulator_group.map(|group| group.anchor) {
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative_previous_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative_previous_anchor;
|
||||
} else {
|
||||
pos = compute_snapped_angle(&mut self.angle, modifiers.lock_angle, modifiers.snap_angle, pos, relative_previous_anchor);
|
||||
}
|
||||
}
|
||||
|
||||
for manipulator_type in [SelectedType::Anchor, SelectedType::InHandle, SelectedType::OutHandle] {
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, manipulator_type);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
||||
});
|
||||
}
|
||||
Some(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
|
||||
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
|
||||
// Get subpath
|
||||
let layer_path = self.path.as_ref()?;
|
||||
let vector_data = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data())?;
|
||||
let subpath = &vector_data.subpaths[self.subpath_index];
|
||||
|
||||
// Abort if only one manipulator group has been placed
|
||||
if fsm == PenToolFsmState::PlacingAnchor && subpath.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().iter();
|
||||
let mut last_manipulator_group = if self.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous_manipulator_group = if self.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
||||
|
||||
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
|
||||
let Some(previous_manipulator_group) = previous_manipulator_group else {
|
||||
return Some(DocumentMessage::AbortTransaction);
|
||||
};
|
||||
|
||||
// Clean up if there are two or more manipulators
|
||||
// Remove the unplaced anchor if in anchor placing mode
|
||||
if fsm == PenToolFsmState::PlacingAnchor {
|
||||
let layer_path = layer_path.clone();
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
||||
});
|
||||
last_manipulator_group = previous_manipulator_group;
|
||||
}
|
||||
|
||||
// Remove the out handle
|
||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
||||
let position = last_manipulator_group.anchor;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
|
||||
return Some(DocumentMessage::CommitTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for PenToolFsmState {
|
||||
type ToolData = PenToolData;
|
||||
@@ -160,7 +487,15 @@ impl Fsm for PenToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
shape_editor,
|
||||
shape_overlay,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -172,19 +507,19 @@ impl Fsm for PenToolFsmState {
|
||||
// When the document has moved / needs to be redraw, re-render the overlays
|
||||
// TODO the overlay system should probably receive this message instead of the tool
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::SelectionChanged) => {
|
||||
// Set the previously selected layers to invisible
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
shape_overlay.layer_overlay_visibility(&document.document_legacy, layer_path.to_vec(), false, responses);
|
||||
}
|
||||
|
||||
// Redraw the overlays of the newly selected layers
|
||||
for layer_path in document.selected_visible_layers() {
|
||||
tool_data.overlay_renderer.render_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.render_subpath_overlays(&shape_editor.selected_shape_state, &document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -199,383 +534,49 @@ impl Fsm for PenToolFsmState {
|
||||
tool_data.should_mirror = false;
|
||||
|
||||
// Perform extension of an existing path
|
||||
if let Some((layer, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||
tool_data.path = Some(layer.to_vec());
|
||||
tool_data.from_start = from_start;
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let mut stop_mirror = || {
|
||||
let subpath = document.document_legacy.layer(layer).ok().and_then(|layer| layer.as_subpath())?;
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&id, _) = if from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer.to_vec(),
|
||||
id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
Some(())
|
||||
};
|
||||
stop_mirror();
|
||||
|
||||
return PenToolFsmState::DraggingHandle;
|
||||
}
|
||||
|
||||
// Deselect layers because we are now creating a new layer
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
// Create a new layer
|
||||
tool_data.path = Some(document.get_path_for_new_layer());
|
||||
tool_data.from_start = false;
|
||||
|
||||
// Get the position and set properties
|
||||
let transform = tool_data
|
||||
.path
|
||||
.as_ref()
|
||||
.and_then(|path| document.document_legacy.multiply_transforms(&path[..path.len() - 1]).ok())
|
||||
.unwrap_or_default();
|
||||
let snapped_position = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let start_position = transform.inverse().transform_point2(snapped_position);
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
// Create the initial shape with a `bez_path` (only contains a moveto initially)
|
||||
if let Some(layer_path) = &tool_data.path {
|
||||
responses.push_back(
|
||||
Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
insert_index: -1,
|
||||
subpath: Default::default(),
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(add_manipulator_group(
|
||||
&tool_data.path,
|
||||
tool_data.from_start,
|
||||
ManipulatorGroup::new_with_handles(start_position, Some(start_position), Some(start_position)),
|
||||
));
|
||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
|
||||
} else {
|
||||
tool_data.create_new_path(document, tool_options.line_weight, global_tool_data.primary_color, input, responses);
|
||||
}
|
||||
|
||||
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||
// If you place the anchor on top of the previous anchor then you break the mirror
|
||||
let mut check_break = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
if let Some((previous_id, previous_anchor)) = previous
|
||||
.as_ref()
|
||||
.and_then(|(&id, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|x| (id, x)))
|
||||
{
|
||||
// Break the control
|
||||
if transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(previous_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
// Remove the point that has just been placed
|
||||
let op = Operation::RemoveManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Move the in handle of the previous anchor to on top of the previous position
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: previous_anchor.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Stop the handles on the last point from mirroring
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: previous_id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// The overlay system cannot detect deleted points so we must just delete all the overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
tool_data.should_mirror = false;
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
check_break().unwrap_or(PenToolFsmState::DraggingHandle)
|
||||
tool_data.check_break(document, transform, shape_overlay, responses);
|
||||
PenToolFsmState::DraggingHandle
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
let last_in = last_manipulator_group.points[inwards_handle].as_ref()?;
|
||||
|
||||
// Close path
|
||||
let transformed_distance_between_squared = transform.transform_point2(last_anchor.position).distance_squared(transform.transform_point2(first_anchor.position));
|
||||
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||
if transformed_distance_between_squared < snap_point_tolerance_squared && previous.is_some() {
|
||||
// Move the in handle of the first point to where the user has placed it
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: first_id,
|
||||
manipulator_type: inwards_handle,
|
||||
position: last_in.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Stop the handles on the first point from mirroring
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: first_id,
|
||||
mirror_angle: false,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Remove the point that has just been placed
|
||||
let op = Operation::RemoveManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
// Push a close path node
|
||||
responses.push_back(add_manipulator_group(&tool_data.path, false, ManipulatorGroup::closed()));
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
|
||||
// Clean up tool data
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
// Return the new tool state, wrapped in `Some()` because this closure returns an Option used by the `?` operation various times above
|
||||
return Some(PenToolFsmState::Ready);
|
||||
}
|
||||
// Add a new manipulator for the next anchor that we will place
|
||||
if let Some(out_handle) = &last_manipulator_group.points[outwards_handle] {
|
||||
responses.push_back(add_manipulator_group(&tool_data.path, tool_data.from_start, ManipulatorGroup::new_with_anchor(out_handle.position)));
|
||||
}
|
||||
|
||||
// Returning `None` means the `unwrap_or` clause below returns the state `PlacingAnchor`
|
||||
None
|
||||
};
|
||||
tool_data.should_mirror = true;
|
||||
process().unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
tool_data.finish_placing_handle(document, transform, shape_overlay, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
|
||||
// Get correct handle types
|
||||
let inwards_handle = if tool_data.from_start { ManipulatorType::OutHandle } else { ManipulatorType::InHandle };
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Get manipulator points
|
||||
let last_anchor = last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let snap_angle = input.keyboard.get(snap_angle as usize);
|
||||
let lock_angle = input.keyboard.get(lock_angle as usize);
|
||||
let pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, last_anchor.position);
|
||||
|
||||
// Update points on current segment (to show preview of new handle)
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
|
||||
let should_mirror = !input.keyboard.get(break_handle as usize) && tool_data.should_mirror;
|
||||
// Mirror handle of last segment
|
||||
if should_mirror {
|
||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
||||
let pos = last_anchor.position - (pos - last_anchor.position);
|
||||
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: inwards_handle,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
|
||||
// Update the mirror status of the currently modifying point
|
||||
let op = Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
mirror_angle: should_mirror,
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
Some(())
|
||||
let modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
if process().is_none() {
|
||||
PenToolFsmState::Ready
|
||||
} else {
|
||||
self
|
||||
}
|
||||
tool_data.drag_handle(document, transform, input.mouse.position, modifiers, responses).unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, lock_angle, .. }) => {
|
||||
let mut process = || {
|
||||
// Get subpath
|
||||
let data = tool_data.clone();
|
||||
let layer_path = data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&last_id, _last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get the first manipulator group
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (_first_id, first_manipulator_group) = if tool_data.from_start { manipulator_groups.next_back()? } else { manipulator_groups.next()? };
|
||||
|
||||
// Get manipulator points
|
||||
let first_anchor = first_manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
|
||||
let mouse = tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
|
||||
let mut pos = transform.inverse().transform_point2(mouse);
|
||||
|
||||
// Snap to the first point (to show close path)
|
||||
if mouse.distance_squared(transform.transform_point2(first_anchor.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = first_anchor.position;
|
||||
}
|
||||
|
||||
if let Some(relative) = previous.as_ref().and_then(|(_, manipulator_group)| manipulator_group.points[ManipulatorType::Anchor].as_ref()) {
|
||||
// Snap to the previously placed point (to show break control)
|
||||
if mouse.distance_squared(transform.transform_point2(relative.position)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
||||
pos = relative.position;
|
||||
} else {
|
||||
let snap_angle = input.keyboard.get(snap_angle as usize);
|
||||
let lock_angle = input.keyboard.get(lock_angle as usize);
|
||||
pos = compute_snapped_angle(&mut tool_data.angle, lock_angle, snap_angle, pos, relative.position);
|
||||
}
|
||||
}
|
||||
|
||||
for manipulator_type in [ManipulatorType::Anchor, ManipulatorType::InHandle, ManipulatorType::OutHandle] {
|
||||
let msg = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type,
|
||||
position: pos.into(),
|
||||
};
|
||||
responses.push_back(msg.into());
|
||||
}
|
||||
|
||||
Some(())
|
||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||
let modifiers = ModifierState {
|
||||
snap_angle: input.keyboard.key(snap_angle),
|
||||
lock_angle: input.keyboard.key(lock_angle),
|
||||
break_handle: input.keyboard.key(break_handle),
|
||||
};
|
||||
if process().is_none() {
|
||||
PenToolFsmState::Ready
|
||||
} else {
|
||||
self
|
||||
}
|
||||
tool_data
|
||||
.place_anchor(document, transform, input.mouse.position, modifiers, responses)
|
||||
.unwrap_or(PenToolFsmState::Ready)
|
||||
}
|
||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||
// Abort or commit the transaction to the undo history
|
||||
let mut commit = || {
|
||||
// Get subpath
|
||||
let layer_path = tool_data.path.as_ref()?;
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
|
||||
// If placing anchor we should abort if there are less than three manipulators (as the last one gets deleted)
|
||||
if self == PenToolFsmState::PlacingAnchor && subpath.manipulator_groups().len() < 3 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Get the last manipulator group and the one previous to that
|
||||
let mut manipulator_groups = subpath.manipulator_groups().enumerate();
|
||||
let (&(mut last_id), mut last_manipulator_group) = if tool_data.from_start { manipulator_groups.next()? } else { manipulator_groups.next_back()? };
|
||||
let previous = if tool_data.from_start { manipulator_groups.next() } else { manipulator_groups.next_back() };
|
||||
|
||||
// Get correct handle types
|
||||
let outwards_handle = if tool_data.from_start { ManipulatorType::InHandle } else { ManipulatorType::OutHandle };
|
||||
|
||||
// Clean up if there are two or more manipulators
|
||||
if let Some((&previous_id, previous_manipulator_group)) = previous {
|
||||
// Remove the unplaced anchor if in anchor placing mode
|
||||
if self == PenToolFsmState::PlacingAnchor {
|
||||
let layer_path = layer_path.clone();
|
||||
let op = Operation::RemoveManipulatorGroup { layer_path, id: last_id };
|
||||
responses.push_back(op.into());
|
||||
last_id = previous_id;
|
||||
last_manipulator_group = previous_manipulator_group;
|
||||
}
|
||||
|
||||
// Remove the out handle
|
||||
let op = Operation::MoveManipulatorPoint {
|
||||
layer_path: layer_path.clone(),
|
||||
id: last_id,
|
||||
manipulator_type: outwards_handle,
|
||||
position: last_manipulator_group.points[ManipulatorType::Anchor].as_ref()?.position.into(),
|
||||
};
|
||||
responses.push_back(op.into());
|
||||
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
|
||||
return Some(());
|
||||
}
|
||||
|
||||
// Abort if only one manipulator group has been placed
|
||||
None
|
||||
};
|
||||
if commit().is_none() {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
}
|
||||
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
|
||||
responses.add(message);
|
||||
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
tool_data.path = None;
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
@@ -585,7 +586,7 @@ impl Fsm for PenToolFsmState {
|
||||
(_, PenToolMessage::Abort) => {
|
||||
// Clean up overlays
|
||||
for layer_path in document.all_layers() {
|
||||
tool_data.overlay_renderer.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
shape_overlay.clear_subpath_overlays(&document.document_legacy, layer_path.to_vec(), responses);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -640,51 +641,44 @@ fn compute_snapped_angle(cached_angle: &mut f64, lock_angle: bool, snap_angle: b
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via an [Operation].
|
||||
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: ManipulatorGroup) -> Message {
|
||||
match (layer_path, from_start) {
|
||||
(Some(layer_path), true) => Operation::PushFrontManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group,
|
||||
}
|
||||
.into(),
|
||||
(Some(layer_path), false) => Operation::PushManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group,
|
||||
}
|
||||
.into(),
|
||||
(None, _) => Message::NoOp,
|
||||
}
|
||||
/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage].
|
||||
fn add_manipulator_group(layer_path: &Option<Vec<LayerId>>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup<ManipulatorGroupId>) -> Message {
|
||||
let Some(layer) = layer_path.clone() else {
|
||||
return Message::NoOp;
|
||||
};
|
||||
let modification = if from_start {
|
||||
VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group }
|
||||
} else {
|
||||
VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }
|
||||
};
|
||||
GraphOperationMessage::Vector { layer, modification }.into()
|
||||
}
|
||||
|
||||
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
|
||||
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], bool)> {
|
||||
fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(&[LayerId], usize, bool)> {
|
||||
let mut best = None;
|
||||
let mut best_distance_squared = tolerance * tolerance;
|
||||
|
||||
for layer_path in document.selected_layers() {
|
||||
(|| {
|
||||
let viewspace = document.document_legacy.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let Ok(viewspace) = document.document_legacy.generate_transform_relative_to_viewport(layer_path) else { continue };
|
||||
|
||||
let subpath = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_subpath())?;
|
||||
let (_first_id, first) = subpath.manipulator_groups().enumerate().next()?;
|
||||
let (_last_id, last) = subpath.manipulator_groups().enumerate().next_back()?;
|
||||
|
||||
if !last.is_close() {
|
||||
for (manipulator_group, from_start) in [(first, true), (last, false)] {
|
||||
if let Some(point) = &manipulator_group.points[ManipulatorType::Anchor] {
|
||||
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
|
||||
|
||||
if distance_squared < best_distance_squared {
|
||||
best = Some((layer_path, from_start));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(vector_data) = document.document_legacy.layer(layer_path).ok().and_then(|layer| layer.as_vector_data()) else { continue };
|
||||
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
|
||||
if subpath.closed() {
|
||||
continue;
|
||||
}
|
||||
|
||||
None::<()>
|
||||
})();
|
||||
for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] {
|
||||
let Some(manipulator_group) = manipulator_group else { break };
|
||||
|
||||
let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos);
|
||||
|
||||
if distance_squared < best_distance_squared {
|
||||
best = Some((layer_path, subpath_index, from_start));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
best
|
||||
|
||||
@@ -2,14 +2,13 @@ use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -37,8 +36,8 @@ pub enum RectangleToolMessage {
|
||||
|
||||
impl PropertyHolder for RectangleTool {}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for RectangleTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for RectangleTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
@@ -100,7 +99,13 @@ impl Fsm for RectangleToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -113,19 +118,17 @@ impl Fsm for RectangleToolFsmState {
|
||||
match (self, event) {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddRect {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
||||
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::boolean_ops::BooleanOperation;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType};
|
||||
@@ -219,24 +218,24 @@ impl PropertyHolder for SelectTool {
|
||||
})),
|
||||
Separator::new(SeparatorDirection::Horizontal, SeparatorType::Section).widget_holder(),
|
||||
IconButton::new("BooleanUnion", 24)
|
||||
.tooltip("Boolean Union")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Union).into())
|
||||
.tooltip("Boolean Union (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanSubtractFront", 24)
|
||||
.tooltip("Boolean Subtract Front")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractFront).into())
|
||||
.tooltip("Boolean Subtract Front (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanSubtractBack", 24)
|
||||
.tooltip("Boolean Subtract Back")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::SubtractBack).into())
|
||||
.tooltip("Boolean Subtract Back (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanIntersect", 24)
|
||||
.tooltip("Boolean Intersect")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Intersection).into())
|
||||
.tooltip("Boolean Intersect (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
IconButton::new("BooleanDifference", 24)
|
||||
.tooltip("Boolean Difference")
|
||||
.on_update(|_| DocumentMessage::BooleanOperation(BooleanOperation::Difference).into())
|
||||
.tooltip("Boolean Difference (coming soon)")
|
||||
.on_update(|_| DialogMessage::RequestComingSoonDialog { issue: Some(1091) }.into())
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
PopoverButton::new("Boolean", "Coming soon").widget_holder(),
|
||||
@@ -245,8 +244,8 @@ impl PropertyHolder for SelectTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message {
|
||||
self.tool_data.nested_selection_behavior = nested_selection_behavior;
|
||||
responses.push_back(ToolMessage::UpdateHints.into());
|
||||
@@ -345,9 +344,10 @@ impl SelectToolData {
|
||||
for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) {
|
||||
// Moves the original back to its starting position.
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_start - self.drag_current).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_start - self.drag_current),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -401,9 +401,10 @@ impl SelectToolData {
|
||||
// Move the original to under the mouse
|
||||
for layer_path in Document::shallowest_unique_layers(originals.iter()) {
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_current - self.drag_start).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: layer_path.clone(),
|
||||
transform: DAffine2::from_translation(self.drag_current - self.drag_start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
@@ -429,7 +430,7 @@ impl Fsm for SelectToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, _global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData { document, input, render_data, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -619,9 +620,10 @@ impl Fsm for SelectToolFsmState {
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for path in Document::shallowest_unique_layers(tool_data.layers_dragging.iter()) {
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move).to_cols_array(),
|
||||
GraphOperationMessage::TransformChange {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_translation(mouse_delta + closest_move),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -3,14 +3,13 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::DAffine2;
|
||||
use glam::DVec2;
|
||||
use graphene_core::vector::style::Fill;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -80,8 +79,8 @@ impl PropertyHolder for ShapeTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for ShapeTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
ShapeOptionsUpdate::Vertices(vertices) => self.options.vertices = vertices,
|
||||
@@ -139,7 +138,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -153,20 +158,16 @@ impl Fsm for ShapeToolFsmState {
|
||||
(Ready, DragStart) => {
|
||||
shape_data.start(responses, document, input, render_data);
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let layer_path = document.get_path_for_new_layer();
|
||||
shape_data.path = Some(layer_path.clone());
|
||||
tool_data.sides = tool_options.vertices;
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNgon {
|
||||
path: shape_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
sides: tool_data.sides,
|
||||
style: style::PathStyle::new(None, style::Fill::solid(global_tool_data.primary_color)),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let subpath = bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_data.sides as u64, 1.);
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer: layer_path,
|
||||
fill: Fill::solid(global_tool_data.primary_color),
|
||||
});
|
||||
|
||||
Drawing
|
||||
}
|
||||
|
||||
@@ -4,15 +4,15 @@ use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMot
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, PropertyHolder, WidgetLayout};
|
||||
use crate::messages::layout::utility_types::widgets::input_widgets::NumberInput;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
use crate::messages::tool::utility_types::{DocumentToolData, EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
|
||||
use document_legacy::layers::style;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_core::vector::style::Stroke;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::DVec2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -86,8 +86,8 @@ impl PropertyHolder for SplineTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
SplineOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
|
||||
@@ -146,7 +146,13 @@ impl Fsm for SplineToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -173,7 +179,7 @@ impl Fsm for SplineToolFsmState {
|
||||
|
||||
tool_data.weight = tool_options.line_weight;
|
||||
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -189,7 +195,7 @@ impl Fsm for SplineToolFsmState {
|
||||
}
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
@@ -199,14 +205,14 @@ impl Fsm for SplineToolFsmState {
|
||||
tool_data.next_point = pos;
|
||||
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, true));
|
||||
add_spline(tool_data, global_tool_data, true, responses);
|
||||
|
||||
Drawing
|
||||
}
|
||||
(Drawing, Confirm) | (Drawing, Abort) => {
|
||||
if tool_data.points.len() >= 2 {
|
||||
responses.push_back(remove_preview(tool_data));
|
||||
responses.push_back(add_spline(tool_data, global_tool_data, false));
|
||||
add_spline(tool_data, global_tool_data, false, responses);
|
||||
responses.push_back(DocumentMessage::CommitTransaction.into());
|
||||
} else {
|
||||
responses.push_back(DocumentMessage::AbortTransaction.into());
|
||||
@@ -249,18 +255,18 @@ fn remove_preview(tool_data: &SplineToolData) -> Message {
|
||||
.into()
|
||||
}
|
||||
|
||||
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool) -> Message {
|
||||
let mut points: Vec<(f64, f64)> = tool_data.points.iter().map(|p| (p.x, p.y)).collect();
|
||||
fn add_spline(tool_data: &SplineToolData, global_tool_data: &DocumentToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
||||
let mut points = tool_data.points.clone();
|
||||
if show_preview {
|
||||
points.push((tool_data.next_point.x, tool_data.next_point.y))
|
||||
points.push(tool_data.next_point)
|
||||
}
|
||||
|
||||
Operation::AddSpline {
|
||||
path: tool_data.path.clone().unwrap(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::IDENTITY.to_cols_array(),
|
||||
points,
|
||||
style: style::PathStyle::new(Some(style::Stroke::new(global_tool_data.primary_color, tool_data.weight)), style::Fill::None),
|
||||
}
|
||||
.into()
|
||||
let subpath = bezier_rs::Subpath::new_cubic_spline(points);
|
||||
|
||||
let layer_path = tool_data.path.clone().unwrap();
|
||||
graph_modification_utils::new_vector_layer(vec![subpath], layer_path.clone(), responses);
|
||||
responses.add(GraphOperationMessage::StrokeSet {
|
||||
layer: layer_path.clone(),
|
||||
stroke: Stroke::new(global_tool_data.primary_color, tool_data.weight),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,8 +127,8 @@ impl PropertyHolder for TextTool {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: ToolActionHandlerData<'a>) {
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
if let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message {
|
||||
match action {
|
||||
TextOptionsUpdate::Font { family, style } => {
|
||||
@@ -266,7 +266,13 @@ impl Fsm for TextToolFsmState {
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
(document, _document_id, global_tool_data, input, render_data): ToolActionHandlerData,
|
||||
ToolActionHandlerData {
|
||||
document,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
..
|
||||
}: &mut ToolActionHandlerData,
|
||||
tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
@@ -300,7 +306,7 @@ impl Fsm for TextToolFsmState {
|
||||
else if state == TextToolFsmState::Ready {
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let transform = DAffine2::from_translation(input.mouse.position).to_cols_array();
|
||||
let transform = DAffine2::from_translation(input.mouse.position);
|
||||
let font_size = tool_options.font_size;
|
||||
let font_name = tool_options.font_name.clone();
|
||||
let font_style = tool_options.font_style.clone();
|
||||
@@ -320,9 +326,10 @@ impl Fsm for TextToolFsmState {
|
||||
.into(),
|
||||
);
|
||||
responses.push_back(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: tool_data.layer_path.clone(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: tool_data.layer_path.clone(),
|
||||
transform,
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::consts::SLOWING_DIVISOR;
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeEditor;
|
||||
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||
|
||||
use document_legacy::layers::style::RenderData;
|
||||
@@ -11,7 +12,7 @@ use glam::DVec2;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
transform_operation: TransformOperation,
|
||||
pub transform_operation: TransformOperation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
@@ -22,22 +23,25 @@ pub struct TransformLayerMessageHandler {
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
|
||||
shape_editor: ShapeEditor,
|
||||
}
|
||||
impl TransformLayerMessageHandler {
|
||||
pub fn is_transforming(&self) -> bool {
|
||||
self.transform_operation != TransformOperation::None
|
||||
}
|
||||
pub fn hints(&self, responses: &mut VecDeque<Message>) {
|
||||
self.transform_operation.hints(self.snap, responses);
|
||||
}
|
||||
}
|
||||
|
||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData);
|
||||
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>, &'a ToolData, &'a mut ShapeState);
|
||||
impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformLayerMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, ipp, render_data, tool_data): TransformData) {
|
||||
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, ipp, render_data, tool_data, shape_editor): TransformData) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
// TODO: Transform individual points when using the path tool.
|
||||
let _using_path_tool = tool_data.active_tool_type == ToolType::Path;
|
||||
|
||||
// You may also want the shape editor here? If not, then feel free to remove.
|
||||
let _shape_editor = &self.shape_editor;
|
||||
|
||||
let selected_layers = document.layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then_some(layer_path)).collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, &document.document_legacy);
|
||||
|
||||
@@ -61,7 +65,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
BeginGrab => {
|
||||
if let TransformOperation::Grabbing(_) = self.transform_operation {
|
||||
@@ -123,7 +128,8 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
|
||||
self.transform_operation = TransformOperation::None;
|
||||
|
||||
responses.push_back(BroadcastEvent::DocumentIsDirty.into());
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
@@ -178,7 +184,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
||||
}
|
||||
SelectionChanged => {
|
||||
let layer_paths = document.selected_visible_layers().map(|layer_path| layer_path.to_vec()).collect();
|
||||
self.shape_editor.set_selected_layers(layer_paths);
|
||||
shape_editor.set_selected_layers(layer_paths);
|
||||
}
|
||||
TypeBackspace => self.transform_operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.transform_operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use super::common_functionality::overlay_renderer::OverlayRenderer;
|
||||
use super::common_functionality::shape_editor::ShapeState;
|
||||
use super::tool_messages::*;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, MouseMotion};
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
@@ -15,10 +17,39 @@ use graphene_core::raster::color::Color;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
pub type ToolActionHandlerData<'a> = (&'a DocumentMessageHandler, u64, &'a DocumentToolData, &'a InputPreprocessorMessageHandler, &'a RenderData<'a>);
|
||||
pub struct ToolActionHandlerData<'a> {
|
||||
pub document: &'a DocumentMessageHandler,
|
||||
pub document_id: u64,
|
||||
pub global_tool_data: &'a DocumentToolData,
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub render_data: &'a RenderData<'a>,
|
||||
pub shape_overlay: &'a mut OverlayRenderer,
|
||||
pub shape_editor: &'a mut ShapeState,
|
||||
}
|
||||
impl<'a> ToolActionHandlerData<'a> {
|
||||
pub fn new(
|
||||
document: &'a DocumentMessageHandler,
|
||||
document_id: u64,
|
||||
global_tool_data: &'a DocumentToolData,
|
||||
input: &'a InputPreprocessorMessageHandler,
|
||||
render_data: &'a RenderData<'a>,
|
||||
shape_overlay: &'a mut OverlayRenderer,
|
||||
shape_editor: &'a mut ShapeState,
|
||||
) -> Self {
|
||||
Self {
|
||||
document,
|
||||
document_id,
|
||||
global_tool_data,
|
||||
input,
|
||||
render_data,
|
||||
shape_overlay,
|
||||
shape_editor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToolCommon: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
|
||||
impl<T> ToolCommon for T where T: for<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
|
||||
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
|
||||
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + PropertyHolder + ToolTransition + ToolMetadata {}
|
||||
|
||||
type Tool = dyn ToolCommon + Send + Sync;
|
||||
|
||||
@@ -41,7 +72,7 @@ pub trait Fsm {
|
||||
/// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages,
|
||||
/// update some internal tool variables, and end by transitioning to a `Drawing` state.
|
||||
#[must_use]
|
||||
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
|
||||
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, messages: &mut VecDeque<Message>) -> Self;
|
||||
|
||||
/// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar.
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>);
|
||||
@@ -70,7 +101,7 @@ pub trait Fsm {
|
||||
&mut self,
|
||||
message: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
transition_data: ToolActionHandlerData,
|
||||
transition_data: &mut ToolActionHandlerData,
|
||||
options: &Self::ToolOptions,
|
||||
messages: &mut VecDeque<Message>,
|
||||
update_cursor_on_transition: bool,
|
||||
@@ -539,6 +570,16 @@ impl HintInfo {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn label(label: impl Into<String>) -> Self {
|
||||
Self {
|
||||
key_groups: vec![],
|
||||
key_groups_mac: None,
|
||||
mouse: None,
|
||||
label: label.into(),
|
||||
plus: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn keys_and_mouse(keys: impl IntoIterator<Item = Key>, mouse_motion: MouseMotion, label: impl Into<String>) -> Self {
|
||||
let keys: Vec<_> = keys.into_iter().collect();
|
||||
Self {
|
||||
|
||||
@@ -6,9 +6,11 @@ use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use dyn_any::DynAny;
|
||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::vector::VectorData;
|
||||
use interpreted_executor::executor::DynamicExecutor;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -20,8 +22,8 @@ pub struct NodeGraphExecutor {
|
||||
}
|
||||
|
||||
impl NodeGraphExecutor {
|
||||
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
|
||||
fn execute_network<T: dyn_any::StaticType>(&mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result<T, String> {
|
||||
/// Execute the network by flattening it and creating a borrow stack.
|
||||
fn execute_network<'a>(&'a mut self, network: NodeNetwork, image_frame: ImageFrame) -> Result<Box<dyn dyn_any::DynAny + 'a>, String> {
|
||||
let mut scoped_network = wrap_network_in_scope(network);
|
||||
|
||||
scoped_network.duplicate_outputs(&mut generate_uuid);
|
||||
@@ -40,9 +42,7 @@ impl NodeGraphExecutor {
|
||||
use dyn_any::IntoDynAny;
|
||||
use graph_craft::executor::Executor;
|
||||
|
||||
let boxed = self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string())?;
|
||||
|
||||
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
||||
self.executor.execute(image_frame.into_dyn()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Computes an input for a node in the graph
|
||||
@@ -82,7 +82,9 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
self.execute_network(network, image_frame.into_owned())
|
||||
let boxed = self.execute_network(network, image_frame.into_owned())?;
|
||||
|
||||
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
||||
}
|
||||
|
||||
/// Encodes an image into a format using the image crate
|
||||
@@ -243,11 +245,24 @@ impl NodeGraphExecutor {
|
||||
}?;
|
||||
let network = node_graph_frame.network.clone();
|
||||
|
||||
// Execute the node graph
|
||||
// Special execution path for generating imaginate (as generation requires io from outside node graph)
|
||||
if let Some(imaginate_node) = imaginate_node {
|
||||
responses.push_back(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, image_frame, persistent_data)?);
|
||||
return Ok(());
|
||||
}
|
||||
// Execute the node graph
|
||||
let boxed_node_graph_output = self.execute_network(network, image_frame)?;
|
||||
|
||||
// Check if the output is vector data
|
||||
if core::any::TypeId::of::<VectorData>() == DynAny::type_id(boxed_node_graph_output.as_ref()) {
|
||||
// Update the cached vector data on the layer
|
||||
let vector_data: VectorData = dyn_any::downcast(boxed_node_graph_output).map(|v| *v)?;
|
||||
let transform = vector_data.transform.to_cols_array();
|
||||
responses.push_back(Operation::SetLayerTransform { path: layer_path.clone(), transform }.into());
|
||||
responses.push_back(Operation::SetVectorData { path: layer_path, vector_data }.into());
|
||||
} else {
|
||||
let ImageFrame { image, transform } = self.execute_network(network, image_frame)?;
|
||||
// Attempt to downcast to an image frame
|
||||
let ImageFrame { image, transform } = dyn_any::downcast(boxed_node_graph_output).map(|image_frame| *image_frame)?;
|
||||
|
||||
// If no image was generated, clear the frame
|
||||
if image.width == 0 || image.height == 0 {
|
||||
|
||||
@@ -123,6 +123,16 @@ impl Bezier {
|
||||
format!("{handle_args} {} {}", self.end.x, self.end.y)
|
||||
}
|
||||
|
||||
/// Write the curve argument to the string
|
||||
pub fn write_curve_argument(&self, svg: &mut String) -> std::fmt::Result {
|
||||
match self.handles {
|
||||
BezierHandles::Linear => svg.push_str(SVG_ARG_LINEAR),
|
||||
BezierHandles::Quadratic { handle } => write!(svg, "{SVG_ARG_QUADRATIC}{},{}", handle.x, handle.y)?,
|
||||
BezierHandles::Cubic { handle_start, handle_end } => write!(svg, "{SVG_ARG_CUBIC}{},{} {},{}", handle_start.x, handle_start.y, handle_end.x, handle_end.y)?,
|
||||
}
|
||||
write!(svg, " {},{}", self.end.x, self.end.y)
|
||||
}
|
||||
|
||||
/// Return the string argument used to create the lines connecting handles to endpoints in an SVG `path`
|
||||
pub(crate) fn svg_handle_line_argument(&self) -> Option<String> {
|
||||
match self.handles {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::utils::TValue;
|
||||
use crate::utils::{solve_cubic, solve_quadratic, TValue};
|
||||
|
||||
use glam::DMat2;
|
||||
use std::ops::Range;
|
||||
@@ -403,6 +403,105 @@ impl Bezier {
|
||||
let handle2 = other.start - other.non_normalized_tangent(0.) / 3.;
|
||||
Bezier::from_cubic_dvec2(self.end, handle1, handle2, other.start)
|
||||
}
|
||||
|
||||
/// Compute the winding order (number of times crossing an infinate line to the left of the point)
|
||||
///
|
||||
/// Assumes curve is split at the extrema.
|
||||
fn pre_split_winding_number(&self, target_point: DVec2) -> i32 {
|
||||
// Clockwise is -1, anticlockwise is +1 (with +y as up)
|
||||
// Looking only to the left (-x) of the target_point
|
||||
let resulting_sign = if self.end.y > self.start.y {
|
||||
if target_point.y < self.start.y || target_point.y >= self.end.y {
|
||||
return 0;
|
||||
}
|
||||
-1
|
||||
} else if self.end.y < self.start.y {
|
||||
if target_point.y < self.end.y || target_point.y >= self.start.y {
|
||||
return 0;
|
||||
}
|
||||
1
|
||||
} else {
|
||||
return 0;
|
||||
};
|
||||
match &self.handles {
|
||||
BezierHandles::Linear => {
|
||||
if target_point.x < self.start.x.min(self.end.x) {
|
||||
return 0;
|
||||
}
|
||||
if target_point.x >= self.start.x.max(self.end.x) {
|
||||
return resulting_sign;
|
||||
}
|
||||
// line equation ax + by = c
|
||||
let a = self.end.y - self.start.y;
|
||||
let b = self.start.x - self.end.x;
|
||||
let c = a * self.start.x + b * self.start.y;
|
||||
if (a * target_point.x + b * target_point.y - c) * (resulting_sign as f64) <= 0.0 {
|
||||
resulting_sign
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
BezierHandles::Quadratic { handle: p1 } => {
|
||||
if target_point.x < self.start.x.min(self.end.x).min(p1.x) {
|
||||
return 0;
|
||||
}
|
||||
if target_point.x >= self.start.x.max(self.end.x).max(p1.x) {
|
||||
return resulting_sign;
|
||||
}
|
||||
let a = self.end.y - 2.0 * p1.y + self.start.y;
|
||||
let b = 2.0 * (p1.y - self.start.y);
|
||||
let c = self.start.y - target_point.y;
|
||||
|
||||
let discriminant = b * b - 4. * a * c;
|
||||
let two_times_a = 2. * a;
|
||||
for t in solve_quadratic(discriminant, two_times_a, b, c) {
|
||||
if (0.0..=1.0).contains(&t) {
|
||||
let x = self.evaluate(TValue::Parametric(t)).x;
|
||||
if target_point.x >= x {
|
||||
return resulting_sign;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
BezierHandles::Cubic { handle_start: p1, handle_end: p2 } => {
|
||||
if target_point.x < self.start.x.min(self.end.x).min(p1.x).min(p2.x) {
|
||||
return 0;
|
||||
}
|
||||
if target_point.x >= self.start.x.max(self.end.x).max(p1.x).max(p2.x) {
|
||||
return resulting_sign;
|
||||
}
|
||||
let a = self.end.y - 3.0 * p2.y + 3.0 * p1.y - self.start.y;
|
||||
let b = 3.0 * (p2.y - 2.0 * p1.y + self.start.y);
|
||||
let c = 3.0 * (p1.y - self.start.y);
|
||||
let d = self.start.y - target_point.y;
|
||||
for t in solve_cubic(a, b, c, d) {
|
||||
if (0.0..=1.0).contains(&t) {
|
||||
let x = self.evaluate(TValue::Parametric(t)).x;
|
||||
if target_point.x >= x {
|
||||
return resulting_sign;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the winding number contribution of a single segment.
|
||||
///
|
||||
/// Cast a ray to the left and count intersections.
|
||||
pub fn winding(&self, target_point: DVec2) -> i32 {
|
||||
let extrema = self.get_extrema_t_list();
|
||||
extrema
|
||||
.windows(2)
|
||||
.map(|t| self.trim(TValue::Parametric(t[0]), TValue::Parametric(t[1])).pre_split_winding_number(target_point))
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -104,7 +104,7 @@ impl Bezier {
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from applying the transformation function to each point in the Bezier.
|
||||
pub fn apply_transformation(&self, transformation_function: &dyn Fn(DVec2) -> DVec2) -> Bezier {
|
||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Bezier {
|
||||
let transformed_start = transformation_function(self.start);
|
||||
let transformed_end = transformation_function(self.end);
|
||||
match self.handles {
|
||||
@@ -125,18 +125,18 @@ impl Bezier {
|
||||
/// <iframe frameBorder="0" width="100%" height="375px" src="https://graphite.rs/bezier-rs-demos#bezier/rotate/solo" title="Rotate Demo"></iframe>
|
||||
pub fn rotate(&self, angle: f64) -> Bezier {
|
||||
let rotation_matrix = DMat2::from_angle(angle);
|
||||
self.apply_transformation(&|point| rotation_matrix.mul_vec2(point))
|
||||
self.apply_transformation(|point| rotation_matrix.mul_vec2(point))
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from rotating the curve around the provided point by the given angle (in radians).
|
||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Bezier {
|
||||
let rotation_matrix = DMat2::from_angle(angle);
|
||||
self.apply_transformation(&|point| rotation_matrix.mul_vec2(point - pivot) + pivot)
|
||||
self.apply_transformation(|point| rotation_matrix.mul_vec2(point - pivot) + pivot)
|
||||
}
|
||||
|
||||
/// Returns a Bezier curve that results from translating the curve by the given `DVec2`.
|
||||
pub fn translate(&self, translation: DVec2) -> Bezier {
|
||||
self.apply_transformation(&|point| point + translation)
|
||||
self.apply_transformation(|point| point + translation)
|
||||
}
|
||||
|
||||
/// Determine if it is possible to scale the given curve, using the following conditions:
|
||||
@@ -163,7 +163,7 @@ impl Bezier {
|
||||
}
|
||||
|
||||
/// Add the bezier endpoints if not already present, and combine and sort the dimensional extrema.
|
||||
fn get_extrema_t_list(&self) -> Vec<f64> {
|
||||
pub(crate) fn get_extrema_t_list(&self) -> Vec<f64> {
|
||||
let mut extrema = self.local_extrema().into_iter().flatten().collect::<Vec<f64>>();
|
||||
extrema.append(&mut vec![0., 1.]);
|
||||
extrema.dedup();
|
||||
@@ -274,7 +274,7 @@ impl Bezier {
|
||||
};
|
||||
|
||||
let should_flip_direction = (self.start - intersection).normalize().abs_diff_eq(normal_start, MAX_ABSOLUTE_DIFFERENCE);
|
||||
intermediate.apply_transformation(&|point| {
|
||||
intermediate.apply_transformation(|point| {
|
||||
let mut direction_unit_vector = (intersection - point).normalize();
|
||||
if should_flip_direction {
|
||||
direction_unit_vector *= -1.;
|
||||
|
||||
@@ -123,6 +123,20 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
let _ = write!(svg, r#"<path d="{} {}" {attributes}/>"#, curve_start_argument, curve_arguments.join(" "));
|
||||
}
|
||||
|
||||
/// Write the curve argument to the string (the d="..." part)
|
||||
pub fn subpath_to_svg(&self, svg: &mut String, transform: glam::DAffine2) -> std::fmt::Result {
|
||||
let start = transform.transform_point2(self[0].anchor);
|
||||
write!(svg, "{SVG_ARG_MOVE}{},{}", start.x, start.y)?;
|
||||
for bezier in self.iter() {
|
||||
bezier.apply_transformation(|pos| transform.transform_point2(pos)).write_curve_argument(svg)?;
|
||||
svg.push(' ');
|
||||
}
|
||||
if self.closed {
|
||||
svg.push_str(SVG_ARG_CLOSED);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Appends to the `svg` mutable string with an SVG shape representation of the handle lines.
|
||||
pub fn handle_lines_to_svg(&self, svg: &mut String, attributes: String) {
|
||||
let handle_lines: Vec<String> = self.iter().filter_map(|bezier| bezier.svg_handle_line_argument()).collect();
|
||||
@@ -178,7 +192,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
Self::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true)
|
||||
}
|
||||
|
||||
/// Constructs an elipse with `corner1` and `corner2` as the two corners of the bounding box.
|
||||
/// Constructs an ellipse with `corner1` and `corner2` as the two corners of the bounding box.
|
||||
pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self {
|
||||
let size = (corner1 - corner2).abs();
|
||||
let center = (corner1 + corner2) / 2.;
|
||||
@@ -192,10 +206,10 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
let handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5;
|
||||
|
||||
let manipulator_groups = vec![
|
||||
ManipulatorGroup::new(top, Some(top + handle_offset * DVec2::X), Some(top - handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(right, Some(right + handle_offset * DVec2::Y), Some(right - handle_offset * DVec2::Y)),
|
||||
ManipulatorGroup::new(bottom, Some(bottom - handle_offset * DVec2::X), Some(bottom + handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(left, Some(left - handle_offset * DVec2::Y), Some(left + handle_offset * DVec2::Y)),
|
||||
ManipulatorGroup::new(top, Some(top - handle_offset * DVec2::X), Some(top + handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(right, Some(right - handle_offset * DVec2::Y), Some(right + handle_offset * DVec2::Y)),
|
||||
ManipulatorGroup::new(bottom, Some(bottom + handle_offset * DVec2::X), Some(bottom - handle_offset * DVec2::X)),
|
||||
ManipulatorGroup::new(left, Some(left + handle_offset * DVec2::Y), Some(left - handle_offset * DVec2::Y)),
|
||||
];
|
||||
Self::new(manipulator_groups, true)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,36 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
self.closed
|
||||
}
|
||||
|
||||
/// Set if the subpath is closed.
|
||||
pub fn set_closed(&mut self, new_closed: bool) {
|
||||
self.closed = new_closed;
|
||||
}
|
||||
|
||||
/// Access a [ManipulatorGroup] from a [ManipulatorGroupId].
|
||||
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access a mutable [ManipulatorGroup] from a [ManipulatorGroupId].
|
||||
pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Access the index of a [ManipulatorGroup] from a [ManipulatorGroupId].
|
||||
pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option<usize> {
|
||||
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
||||
}
|
||||
|
||||
/// Insert a manipulator group at an index
|
||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<ManipulatorGroupId>) {
|
||||
self.manipulator_groups.insert(index, group)
|
||||
}
|
||||
|
||||
/// Remove a manipulator group at an index
|
||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<ManipulatorGroupId> {
|
||||
self.manipulator_groups.remove(index)
|
||||
}
|
||||
|
||||
/// Inserts a `ManipulatorGroup` at a certain point along the subpath based on the parametric `t`-value provided.
|
||||
/// Expects `t` to be within the inclusive range `[0, 1]`.
|
||||
pub fn insert(&mut self, t: SubpathTValue) {
|
||||
|
||||
@@ -103,6 +103,13 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
self.iter().map(|bezier| bezier.bounding_box()).reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Return the min and max corners that represent the bounding box of the subpath, after a given affine transform.
|
||||
pub fn bounding_box_with_transform(&self, transform: glam::DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.iter()
|
||||
.map(|bezier| bezier.apply_transformation(&|v| transform.transform_point2(v)).bounding_box())
|
||||
.reduce(|bbox1, bbox2| [bbox1[0].min(bbox2[0]), bbox1[1].max(bbox2[1])])
|
||||
}
|
||||
|
||||
/// Returns list of `t`-values representing the inflection points of the subpath.
|
||||
/// The list of `t`-values returned are filtered such that they fall within the range `[0, 1]`.
|
||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/bezier-rs-demos#subpath/inflections/solo" title="Inflections Demo"></iframe>
|
||||
@@ -123,6 +130,11 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
||||
// TODO: Consider the shared point between adjacent beziers.
|
||||
inflection_t_values
|
||||
}
|
||||
|
||||
/// Does a path contain a point? Based on the non zero winding
|
||||
pub fn contains_point(&self, target_point: DVec2) -> bool {
|
||||
self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>() != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,26 +7,31 @@ use crate::vector::VectorData;
|
||||
use crate::Node;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformNode<Translation, Rotation, Scale, Shear> {
|
||||
pub struct TransformNode<Translation, Rotation, Scale, Shear, Pivot> {
|
||||
pub(crate) translate: Translation,
|
||||
pub(crate) rotate: Rotation,
|
||||
pub(crate) scale: Scale,
|
||||
pub(crate) shear: Shear,
|
||||
pub(crate) pivot: Pivot,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(TransformNode)]
|
||||
pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2) -> VectorData {
|
||||
let transform = generate_transform(shear, &vector_data.transform, scale, rotate, translate);
|
||||
vector_data.transform = transform * vector_data.transform;
|
||||
pub(crate) fn transform_vector_data(mut vector_data: VectorData, translate: DVec2, rotate: f64, scale: DVec2, shear: DVec2, pivot: DVec2) -> VectorData {
|
||||
let pivot = DAffine2::from_translation(vector_data.local_pivot(pivot));
|
||||
|
||||
let modification = pivot * DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]) * pivot.inverse();
|
||||
vector_data.transform = modification * vector_data.transform;
|
||||
|
||||
vector_data
|
||||
}
|
||||
|
||||
impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input> Node<'input, ImageFrame> for TransformNode<Translation, Rotation, Scale, Shear>
|
||||
impl<'input, Translation: 'input, Rotation: 'input, Scale: 'input, Shear: 'input, Pivot: 'input> Node<'input, ImageFrame> for TransformNode<Translation, Rotation, Scale, Shear, Pivot>
|
||||
where
|
||||
Translation: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Rotation: for<'any_input> Node<'any_input, (), Output = f64>,
|
||||
Scale: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Shear: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
Pivot: for<'any_input> Node<'any_input, (), Output = DVec2>,
|
||||
{
|
||||
type Output = ImageFrame;
|
||||
#[inline]
|
||||
@@ -48,6 +53,5 @@ fn generate_transform(shear: DVec2, transform: &DAffine2, scale: DVec2, rotate:
|
||||
let pivot = transform.transform_point2(DVec2::splat(0.5));
|
||||
let translate_to_center = DAffine2::from_translation(-pivot);
|
||||
|
||||
let transformation = translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center;
|
||||
transformation
|
||||
translate_to_center.inverse() * DAffine2::from_scale_angle_translation(scale, rotate, translate) * shear_matrix * translate_to_center
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Copy, Serialize, Deserialize, specta::Type)]
|
||||
@@ -70,7 +71,7 @@ mod uuid_generation {
|
||||
|
||||
pub use uuid_generation::*;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorGroupId(u64);
|
||||
|
||||
|
||||
@@ -17,41 +17,43 @@ pub struct UnitSquareGenerator;
|
||||
|
||||
#[node_macro::node_fn(UnitSquareGenerator)]
|
||||
fn unit_square(_input: ()) -> VectorData {
|
||||
super::VectorData::from_subpath(Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE))
|
||||
super::VectorData::from_subpaths(vec![Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)])
|
||||
}
|
||||
|
||||
// TODO: I removed the Arc requirement we shouuld think about when it makes sense to use its
|
||||
// vs making a generic value node
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator;
|
||||
pub struct PathGenerator<Mirror> {
|
||||
mirror: Mirror,
|
||||
}
|
||||
|
||||
#[node_macro::node_fn(PathGenerator)]
|
||||
fn generate_path(path_data: Subpath<ManipulatorGroupId>) -> super::VectorData {
|
||||
super::VectorData::from_subpath(path_data)
|
||||
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, mirror: Vec<ManipulatorGroupId>) -> super::VectorData {
|
||||
let mut vector_data = super::VectorData::from_subpaths(path_data);
|
||||
vector_data.mirror_angle = mirror;
|
||||
vector_data
|
||||
}
|
||||
|
||||
use crate::raster::Image;
|
||||
// #[derive(Debug, Clone, Copy)]
|
||||
// pub struct BlitSubpath<P> {
|
||||
// path_data: P,
|
||||
// }
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlitSubpath<P> {
|
||||
path_data: P,
|
||||
}
|
||||
// #[node_macro::node_fn(BlitSubpath)]
|
||||
// fn blit_subpath(base_image: Image, path_data: VectorData) -> Image {
|
||||
// // TODO: Get forma to compile
|
||||
// use forma::prelude::*;
|
||||
// let composition = Composition::new();
|
||||
// let mut renderer = cpu::Renderer::new();
|
||||
// let mut path_builder = PathBuilder::new();
|
||||
// for path_segment in path_data.bezier_iter() {
|
||||
// let points = path_segment.internal.get_points().collect::<Vec<_>>();
|
||||
// match points.len() {
|
||||
// 2 => path_builder.line_to(points[1].into()),
|
||||
// 3 => path_builder.quad_to(points[1].into(), points[2].into()),
|
||||
// 4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
|
||||
// }
|
||||
// }
|
||||
|
||||
#[node_macro::node_fn(BlitSubpath)]
|
||||
fn bilt_subpath(base_image: Image, path_data: VectorData) -> Image {
|
||||
// TODO: Get forma to compile
|
||||
/*use forma::prelude::*;
|
||||
let composition = Composition::new();
|
||||
let mut renderer = cpu::Renderer::new();
|
||||
let mut path_builder = PathBuilder::new();
|
||||
for path_segment in path_data.bezier_iter() {
|
||||
let points = path_segment.internal.get_points().collect::<Vec<_>>();
|
||||
match points.len() {
|
||||
2 => path_builder.line_to(points[1].into()),
|
||||
3 => path_builder.quad_to(points[1].into(), points[2].into()),
|
||||
4 => path_builder.cubic_to(points[1].into(), points[2].into(), points[3].into()),
|
||||
}
|
||||
}*/
|
||||
|
||||
base_image
|
||||
}
|
||||
// base_image
|
||||
// }
|
||||
|
||||
@@ -10,7 +10,7 @@ pub mod subpath;
|
||||
pub use subpath::Subpath;
|
||||
|
||||
mod vector_data;
|
||||
pub use vector_data::VectorData;
|
||||
pub use vector_data::*;
|
||||
|
||||
mod id_vec;
|
||||
pub use id_vec::IdBackedVec;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::uuid::ManipulatorGroupId;
|
||||
|
||||
use super::consts::ManipulatorType;
|
||||
use super::id_vec::IdBackedVec;
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
@@ -45,6 +47,21 @@ impl Subpath {
|
||||
shape.path_elements(0.1).into()
|
||||
}
|
||||
|
||||
/// Convert to the legacy Subpath from the `bezier_rs::Subpath`.
|
||||
pub fn from_bezier_crate(value: &[bezier_rs::Subpath<ManipulatorGroupId>]) -> Self {
|
||||
let mut groups = IdBackedVec::new();
|
||||
for subpath in value {
|
||||
for group in subpath.manipulator_groups() {
|
||||
groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle));
|
||||
}
|
||||
if subpath.closed() {
|
||||
let group = subpath.manipulator_groups()[0];
|
||||
groups.push(ManipulatorGroup::new_with_handles(group.anchor, group.in_handle, group.out_handle));
|
||||
}
|
||||
}
|
||||
Self(groups)
|
||||
}
|
||||
|
||||
// ** PRIMITIVE CONSTRUCTION **
|
||||
|
||||
/// constructs a rectangle with `p1` as the lower left and `p2` as the top right
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::style::{PathStyle, Stroke};
|
||||
use crate::{uuid::ManipulatorGroupId, Color};
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::DAffine2;
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
/// [VectorData] is passed between nodes.
|
||||
/// It contains a list of subpaths (that may be open or closed), a transform and some style information.
|
||||
@@ -12,22 +13,146 @@ pub struct VectorData {
|
||||
pub subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>,
|
||||
pub transform: DAffine2,
|
||||
pub style: PathStyle,
|
||||
pub mirror_angle: Vec<ManipulatorGroupId>,
|
||||
}
|
||||
|
||||
impl VectorData {
|
||||
/// An empty subpath with no data, an identity transform and a black fill.
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
subpaths: Vec::new(),
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::Solid(Color::BLACK)),
|
||||
style: PathStyle::new(Some(Stroke::new(Color::BLACK, 0.)), super::style::Fill::None),
|
||||
mirror_angle: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over the manipulator groups of the subpaths
|
||||
pub fn manipulator_groups(&self) -> impl Iterator<Item = &ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
|
||||
self.subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
|
||||
}
|
||||
|
||||
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
|
||||
}
|
||||
|
||||
/// Construct some new vector data from a single subpath with an identy transform and black fill.
|
||||
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
|
||||
super::VectorData {
|
||||
subpaths: vec![subpath],
|
||||
transform: DAffine2::IDENTITY,
|
||||
style: PathStyle::default(),
|
||||
Self::from_subpaths(vec![subpath])
|
||||
}
|
||||
|
||||
/// Construct some new vector data from subpaths with an identy transform and black fill.
|
||||
pub fn from_subpaths(subpaths: Vec<bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
|
||||
super::VectorData { subpaths, ..Self::empty() }
|
||||
}
|
||||
|
||||
/// Compute the bounding boxes of the subpaths without any transform
|
||||
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box_with_transform(DAffine2::IDENTITY)
|
||||
}
|
||||
|
||||
/// Compute the bounding boxes of the subpaths with the specified transform
|
||||
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box_with_transform(transform))
|
||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||
}
|
||||
|
||||
/// Calculate the corners of the bounding box but with a nonzero size.
|
||||
///
|
||||
/// If the layer bounds are `0` in either axis then they are changed to be `1`.
|
||||
pub fn nonzero_bounding_box(&self) -> [DVec2; 2] {
|
||||
let [bounds_min, mut bounds_max] = self.bounding_box().unwrap_or_default();
|
||||
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
if bounds_size.x < 1e-10 {
|
||||
bounds_max.x = bounds_min.x + 1.;
|
||||
}
|
||||
if bounds_size.y < 1e-10 {
|
||||
bounds_max.y = bounds_min.y + 1.;
|
||||
}
|
||||
|
||||
[bounds_min, bounds_max]
|
||||
}
|
||||
|
||||
/// Compute the pivot of the layer in layerspace (the coordinates of the subpaths)
|
||||
pub fn layerspace_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
let [bounds_min, bounds_max] = self.nonzero_bounding_box();
|
||||
let bounds_size = bounds_max - bounds_min;
|
||||
bounds_min + bounds_size * normalised_pivot
|
||||
}
|
||||
|
||||
/// Compute the pivot in local space with the current transform applied
|
||||
pub fn local_pivot(&self, normalised_pivot: DVec2) -> DVec2 {
|
||||
self.transform.transform_point2(self.layerspace_pivot(normalised_pivot))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VectorData {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct ManipulatorPointId {
|
||||
pub group: ManipulatorGroupId,
|
||||
pub manipulator_type: SelectedType,
|
||||
}
|
||||
impl ManipulatorPointId {
|
||||
pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self {
|
||||
Self { group, manipulator_type }
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum SelectedType {
|
||||
Anchor = 1 << 0,
|
||||
InHandle = 1 << 1,
|
||||
OutHandle = 1 << 2,
|
||||
}
|
||||
impl SelectedType {
|
||||
/// Get the location of the [SelectedType] in the [ManipulatorGroup]
|
||||
pub fn get_position(&self, manipulator_group: &ManipulatorGroup<ManipulatorGroupId>) -> Option<DVec2> {
|
||||
match self {
|
||||
Self::Anchor => Some(manipulator_group.anchor),
|
||||
Self::InHandle => manipulator_group.in_handle,
|
||||
Self::OutHandle => manipulator_group.out_handle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the closest [SelectedType] in the [ManipulatorGroup].
|
||||
pub fn closest_widget(manipulator_group: &ManipulatorGroup<ManipulatorGroupId>, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) {
|
||||
let anchor = transform_space.transform_point2(manipulator_group.anchor);
|
||||
// Skip handles under the anchor
|
||||
let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2);
|
||||
let compute_distance = |selected_type: Self| {
|
||||
selected_type.get_position(manipulator_group).and_then(|position| {
|
||||
Some((selected_type, transform_space.transform_point2(position)))
|
||||
.filter(not_under_anchor)
|
||||
.map(|(selected_type, pos)| (selected_type, pos.distance_squared(target)))
|
||||
})
|
||||
};
|
||||
[Self::Anchor, Self::InHandle, Self::OutHandle]
|
||||
.into_iter()
|
||||
.filter_map(compute_distance)
|
||||
.min_by(|a, b| a.1.total_cmp(&b.1))
|
||||
.unwrap_or((Self::Anchor, manipulator_group.anchor.distance_squared(target)))
|
||||
}
|
||||
|
||||
/// Opposite handle
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
SelectedType::Anchor => SelectedType::Anchor,
|
||||
SelectedType::InHandle => SelectedType::OutHandle,
|
||||
SelectedType::OutHandle => SelectedType::InHandle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if handle
|
||||
pub fn is_handle(self) -> bool {
|
||||
self != SelectedType::Anchor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct SetFillNode<FillType, SolidColor, GradientType, Start, End, Transform
|
||||
fn set_vector_data_fill(
|
||||
mut vector_data: VectorData,
|
||||
fill_type: FillType,
|
||||
solid_color: Color,
|
||||
solid_color: Option<Color>,
|
||||
gradient_type: GradientType,
|
||||
start: DVec2,
|
||||
end: DVec2,
|
||||
@@ -26,8 +26,7 @@ fn set_vector_data_fill(
|
||||
positions: Vec<(f64, Option<Color>)>,
|
||||
) -> VectorData {
|
||||
vector_data.style.set_fill(match fill_type {
|
||||
FillType::None => Fill::None,
|
||||
FillType::Solid => Fill::Solid(solid_color),
|
||||
FillType::None | FillType::Solid => solid_color.map_or(Fill::None, |solid_color| Fill::Solid(solid_color)),
|
||||
FillType::Gradient => Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
|
||||
@@ -123,27 +123,25 @@ impl DocumentNode {
|
||||
}
|
||||
|
||||
/// Represents the possible inputs to a node.
|
||||
/// # ShortCircuting
|
||||
///
|
||||
/// # Short circuting
|
||||
///
|
||||
/// In Graphite nodes are functions and by default, these are composed into a single function
|
||||
/// by inserting Compose nodes.
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
/// ```text
|
||||
/// ┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||
/// │ │◄──────────────┤ │◄───────────────┤ │
|
||||
/// │ A │ │ B │ │ C │
|
||||
/// │ ├──────────────►│ ├───────────────►│ │
|
||||
/// └─────────────────┘ └──────────────────┘ └──────────────────┘
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
///
|
||||
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`)
|
||||
/// This is equivalent to calling c(b(a(input))) when evaluating c with input ( `c.eval(input)`).
|
||||
/// But sometimes we might want to have a little more control over the order of execution.
|
||||
/// This is why we allow nodes to opt out of the input forwarding by consuming the input directly.
|
||||
///
|
||||
///
|
||||
///
|
||||
/// ```text
|
||||
/// ┌─────────────────────┐ ┌─────────────┐
|
||||
/// │ │◄───────────────┤ │
|
||||
/// │ Cache Node │ │ C │
|
||||
@@ -153,20 +151,26 @@ impl DocumentNode {
|
||||
/// │ A │ │ * Cached Node │
|
||||
/// │ ├──────────────►│ │
|
||||
/// └──────────────────┘ └─────────────────────┘
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
/// In this case the Cache node actually consumes it's input and then manually forwards it to it's parameter
|
||||
/// Node. This is necessary because the Cache Node needs to short-circut the actual node evaluation
|
||||
/// In this case the Cache node actually consumes its input and then manually forwards it to its parameter Node.
|
||||
/// This is necessary because the Cache Node needs to short-circut the actual node evaluation.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NodeInput {
|
||||
Node { node_id: NodeId, output_index: usize, lambda: bool },
|
||||
Value { tagged_value: crate::document::value::TaggedValue, exposed: bool },
|
||||
Node {
|
||||
node_id: NodeId,
|
||||
output_index: usize,
|
||||
lambda: bool,
|
||||
},
|
||||
Value {
|
||||
tagged_value: crate::document::value::TaggedValue,
|
||||
exposed: bool,
|
||||
},
|
||||
Network(Type),
|
||||
// A short circuting input represents an input that is not resolved through function composition but
|
||||
// actually consuming the provided input instead of passing it to its predecessor
|
||||
/// A short circuting input represents an input that is not resolved through function composition
|
||||
/// but actually consuming the provided input instead of passing it to its predecessor.
|
||||
/// See [NodeInput] docs for more explanation.
|
||||
ShortCircut(Type),
|
||||
}
|
||||
|
||||
@@ -293,7 +297,7 @@ impl NodeNetwork {
|
||||
let mut duplicating_nodes = HashMap::new();
|
||||
// Find the nodes where the inputs require duplicating
|
||||
for node in &mut self.nodes.values_mut() {
|
||||
// Recursivly duplicate children
|
||||
// Recursively duplicate children
|
||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||
network.duplicate_outputs(gen_id);
|
||||
}
|
||||
@@ -420,7 +424,6 @@ impl NodeNetwork {
|
||||
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
|
||||
}
|
||||
NodeInput::Value { tagged_value, exposed } => {
|
||||
// Skip formatting very large values for seconds in performance speedup
|
||||
let name = "Value".to_string();
|
||||
let new_id = map_ids(id, gen_id());
|
||||
let value_node = DocumentNode {
|
||||
@@ -588,6 +591,38 @@ impl NodeNetwork {
|
||||
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
||||
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
||||
}
|
||||
|
||||
/// A iterator of all nodes connected by primary inputs.
|
||||
///
|
||||
/// Used for the properties panel and tools.
|
||||
pub fn primary_flow(&self) -> impl Iterator<Item = (&DocumentNode, u64)> {
|
||||
struct FlowIter<'a> {
|
||||
stack: Vec<NodeId>,
|
||||
network: &'a NodeNetwork,
|
||||
}
|
||||
impl<'a> Iterator for FlowIter<'a> {
|
||||
type Item = (&'a DocumentNode, NodeId);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let node_id = self.stack.pop()?;
|
||||
if let Some(document_node) = self.network.nodes.get(&node_id) {
|
||||
self.stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
return Some((document_node, node_id));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
FlowIter {
|
||||
stack: self.outputs.iter().map(|output| output.node_id).collect(),
|
||||
network: &self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -27,7 +27,7 @@ pub enum TaggedValue {
|
||||
RcImage(Option<Arc<graphene_core::raster::Image>>),
|
||||
ImageFrame(graphene_core::raster::ImageFrame),
|
||||
Color(graphene_core::raster::color::Color),
|
||||
Subpath(bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>),
|
||||
Subpaths(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
||||
RcSubpath(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
||||
BlendMode(BlendMode),
|
||||
LuminanceCalculation(LuminanceCalculation),
|
||||
@@ -45,141 +45,65 @@ pub enum TaggedValue {
|
||||
GradientType(graphene_core::vector::style::GradientType),
|
||||
GradientPositions(Vec<(f64, Option<graphene_core::Color>)>),
|
||||
Quantization(graphene_core::quantization::QuantizationChannels),
|
||||
OptionalColor(Option<graphene_core::raster::color::Color>),
|
||||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
}
|
||||
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
impl Hash for TaggedValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
core::mem::discriminant(self).hash(state);
|
||||
match self {
|
||||
Self::None => 0.hash(state),
|
||||
Self::String(s) => {
|
||||
1.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::U32(u) => {
|
||||
2.hash(state);
|
||||
u.hash(state)
|
||||
}
|
||||
Self::F32(f) => {
|
||||
3.hash(state);
|
||||
f.to_bits().hash(state)
|
||||
}
|
||||
Self::F64(f) => {
|
||||
4.hash(state);
|
||||
f.to_bits().hash(state)
|
||||
}
|
||||
Self::Bool(b) => {
|
||||
5.hash(state);
|
||||
b.hash(state)
|
||||
}
|
||||
Self::DVec2(v) => {
|
||||
6.hash(state);
|
||||
v.to_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||
}
|
||||
Self::OptionalDVec2(None) => 7.hash(state),
|
||||
Self::None => {}
|
||||
Self::String(s) => s.hash(state),
|
||||
Self::U32(u) => u.hash(state),
|
||||
Self::F32(f) => f.to_bits().hash(state),
|
||||
Self::F64(f) => f.to_bits().hash(state),
|
||||
Self::Bool(b) => b.hash(state),
|
||||
Self::DVec2(v) => v.to_array().iter().for_each(|x| x.to_bits().hash(state)),
|
||||
Self::OptionalDVec2(None) => 0.hash(state),
|
||||
Self::OptionalDVec2(Some(v)) => {
|
||||
8.hash(state);
|
||||
1.hash(state);
|
||||
Self::DVec2(*v).hash(state)
|
||||
}
|
||||
Self::DAffine2(m) => {
|
||||
9.hash(state);
|
||||
m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||
}
|
||||
Self::Image(i) => {
|
||||
10.hash(state);
|
||||
i.hash(state)
|
||||
}
|
||||
Self::RcImage(i) => {
|
||||
11.hash(state);
|
||||
i.hash(state)
|
||||
}
|
||||
Self::Color(c) => {
|
||||
12.hash(state);
|
||||
c.hash(state)
|
||||
}
|
||||
Self::Subpath(s) => {
|
||||
13.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::RcSubpath(s) => {
|
||||
14.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::BlendMode(b) => {
|
||||
15.hash(state);
|
||||
b.hash(state)
|
||||
}
|
||||
Self::LuminanceCalculation(l) => {
|
||||
16.hash(state);
|
||||
l.hash(state)
|
||||
}
|
||||
Self::ImaginateSamplingMethod(m) => {
|
||||
17.hash(state);
|
||||
m.hash(state)
|
||||
}
|
||||
Self::ImaginateMaskStartingFill(f) => {
|
||||
18.hash(state);
|
||||
f.hash(state)
|
||||
}
|
||||
Self::ImaginateStatus(s) => {
|
||||
19.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::LayerPath(p) => {
|
||||
20.hash(state);
|
||||
p.hash(state)
|
||||
}
|
||||
Self::DAffine2(m) => m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)),
|
||||
Self::Image(i) => i.hash(state),
|
||||
Self::RcImage(i) => i.hash(state),
|
||||
Self::Color(c) => c.hash(state),
|
||||
Self::Subpaths(s) => s.iter().for_each(|subpath| subpath.hash(state)),
|
||||
Self::RcSubpath(s) => s.hash(state),
|
||||
Self::BlendMode(b) => b.hash(state),
|
||||
Self::LuminanceCalculation(l) => l.hash(state),
|
||||
Self::ImaginateSamplingMethod(m) => m.hash(state),
|
||||
Self::ImaginateMaskStartingFill(f) => f.hash(state),
|
||||
Self::ImaginateStatus(s) => s.hash(state),
|
||||
Self::LayerPath(p) => p.hash(state),
|
||||
Self::ImageFrame(i) => {
|
||||
21.hash(state);
|
||||
i.image.hash(state);
|
||||
i.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||
}
|
||||
Self::VectorData(vector_data) => {
|
||||
22.hash(state);
|
||||
vector_data.subpaths.hash(state);
|
||||
vector_data.transform.to_cols_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||
vector_data.style.hash(state);
|
||||
}
|
||||
Self::Fill(fill) => {
|
||||
23.hash(state);
|
||||
fill.hash(state);
|
||||
}
|
||||
Self::Stroke(stroke) => {
|
||||
24.hash(state);
|
||||
stroke.hash(state);
|
||||
}
|
||||
Self::VecF32(vec_f32) => {
|
||||
25.hash(state);
|
||||
vec_f32.iter().for_each(|val| val.to_bits().hash(state));
|
||||
}
|
||||
Self::LineCap(line_cap) => {
|
||||
26.hash(state);
|
||||
line_cap.hash(state);
|
||||
}
|
||||
Self::LineJoin(line_join) => {
|
||||
27.hash(state);
|
||||
line_join.hash(state);
|
||||
}
|
||||
Self::FillType(fill_type) => {
|
||||
28.hash(state);
|
||||
fill_type.hash(state);
|
||||
}
|
||||
Self::GradientType(gradient_type) => {
|
||||
29.hash(state);
|
||||
gradient_type.hash(state);
|
||||
}
|
||||
Self::Fill(fill) => fill.hash(state),
|
||||
Self::Stroke(stroke) => stroke.hash(state),
|
||||
Self::VecF32(vec_f32) => vec_f32.iter().for_each(|val| val.to_bits().hash(state)),
|
||||
Self::LineCap(line_cap) => line_cap.hash(state),
|
||||
Self::LineJoin(line_join) => line_join.hash(state),
|
||||
Self::FillType(fill_type) => fill_type.hash(state),
|
||||
Self::GradientType(gradient_type) => gradient_type.hash(state),
|
||||
Self::GradientPositions(gradient_positions) => {
|
||||
30.hash(state);
|
||||
gradient_positions.len().hash(state);
|
||||
for (position, color) in gradient_positions {
|
||||
position.to_bits().hash(state);
|
||||
color.hash(state);
|
||||
}
|
||||
}
|
||||
Self::Quantization(quantized_image) => {
|
||||
31.hash(state);
|
||||
quantized_image.hash(state);
|
||||
}
|
||||
Self::Quantization(quantized_image) => quantized_image.hash(state),
|
||||
Self::OptionalColor(color) => color.hash(state),
|
||||
Self::ManipulatorGroupIds(mirror) => mirror.hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,7 +125,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::RcImage(x) => Box::new(x),
|
||||
TaggedValue::ImageFrame(x) => Box::new(x),
|
||||
TaggedValue::Color(x) => Box::new(x),
|
||||
TaggedValue::Subpath(x) => Box::new(x),
|
||||
TaggedValue::Subpaths(x) => Box::new(x),
|
||||
TaggedValue::RcSubpath(x) => Box::new(x),
|
||||
TaggedValue::BlendMode(x) => Box::new(x),
|
||||
TaggedValue::LuminanceCalculation(x) => Box::new(x),
|
||||
@@ -219,6 +143,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::GradientType(x) => Box::new(x),
|
||||
TaggedValue::GradientPositions(x) => Box::new(x),
|
||||
TaggedValue::Quantization(x) => Box::new(x),
|
||||
TaggedValue::OptionalColor(x) => Box::new(x),
|
||||
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +164,7 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::RcImage(_) => concrete!(Option<Arc<graphene_core::raster::Image>>),
|
||||
TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame),
|
||||
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
|
||||
TaggedValue::Subpath(_) => concrete!(bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>),
|
||||
TaggedValue::Subpaths(_) => concrete!(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
||||
TaggedValue::RcSubpath(_) => concrete!(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
||||
TaggedValue::BlendMode(_) => concrete!(BlendMode),
|
||||
TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod),
|
||||
@@ -257,6 +183,8 @@ impl<'a> TaggedValue {
|
||||
TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType),
|
||||
TaggedValue::GradientPositions(_) => concrete!(Vec<(f64, Option<graphene_core::Color>)>),
|
||||
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
||||
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
||||
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,12 +609,12 @@ mod test {
|
||||
assert_eq!(
|
||||
ids,
|
||||
vec![
|
||||
7332206428857154453,
|
||||
946497269036214321,
|
||||
3038115864048241698,
|
||||
1932610308557160863,
|
||||
2105748431407297710,
|
||||
8596220090685862327
|
||||
10739226043134366700,
|
||||
17332796976541881019,
|
||||
7897288931440576543,
|
||||
7388412494950743023,
|
||||
359700384277940942,
|
||||
12822947441562012352
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ where
|
||||
fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output {
|
||||
{
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}"));
|
||||
let input: Box<_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyRefNode Input, {e} in:\n{node_name}"));
|
||||
Box::new(self.node.eval(*input))
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ where
|
||||
fn eval<'node: 'input>(&'node self, input: Any<'input>) -> Self::Output {
|
||||
{
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyNode Input, {e} in:\n{node_name}"));
|
||||
let input: Box<&_I> = dyn_any::downcast(input).unwrap_or_else(|e| panic!("DynAnyInRefNode Input, {e} in:\n{node_name}"));
|
||||
Box::new(self.node.eval(*input))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,16 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
},
|
||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(ImageFrame))]),
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::memo::EndLetNode<_>"),
|
||||
|args| {
|
||||
let input: DowncastBothNode<(), VectorData> = DowncastBothNode::new(args[0]);
|
||||
let node = graphene_std::memo::EndLetNode::new(input);
|
||||
let any: DynAnyInRefNode<ImageFrame, _, _> = graphene_std::any::DynAnyInRefNode::new(node);
|
||||
any.into_type_erased()
|
||||
},
|
||||
NodeIOTypes::new(generic!(T), concrete!(ImageFrame), vec![(concrete!(()), concrete!(VectorData))]),
|
||||
),
|
||||
(
|
||||
NodeIdentifier::new("graphene_std::memo::RefNode<_, _>"),
|
||||
|args| {
|
||||
@@ -336,15 +346,15 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
|
||||
raster_node!(graphene_core::quantization::QuantizeNode<_>, params: [QuantizationChannels]),
|
||||
raster_node!(graphene_core::quantization::DeQuantizeNode<_>, params: [QuantizationChannels]),
|
||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, graphene_core::Color, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option<graphene_core::Color>)>]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: VectorData, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::transform::TransformNode<_, _, _, _, _>, input: ImageFrame, params: [DVec2, f64, DVec2, DVec2, DVec2]),
|
||||
register_node!(graphene_core::vector::SetFillNode<_, _, _, _, _, _, _>, input: VectorData, params: [ graphene_core::vector::style::FillType, Option<graphene_core::Color>, graphene_core::vector::style::GradientType, DVec2, DVec2, DAffine2, Vec<(f64, Option<graphene_core::Color>)>]),
|
||||
register_node!(graphene_core::vector::SetStrokeNode<_, _, _, _, _, _, _>, input: VectorData, params: [graphene_core::Color, f64, Vec<f32>, f64, graphene_core::vector::style::LineCap, graphene_core::vector::style::LineJoin, f64]),
|
||||
register_node!(graphene_core::vector::generator_nodes::UnitCircleGenerator, input: (), params: []),
|
||||
register_node!(
|
||||
graphene_core::vector::generator_nodes::PathGenerator,
|
||||
input: graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>,
|
||||
params: []
|
||||
graphene_core::vector::generator_nodes::PathGenerator<_>,
|
||||
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
||||
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
||||
),
|
||||
];
|
||||
let mut map: HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
|
||||
|
||||
Reference in New Issue
Block a user