mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 01:38:12 +08:00
Add nondestructive vector editing (#1676)
* Initial vector modify node * Initial extraction of data from monitor nodes * Migrate to point id * Start converting to modify node * Non destructive spline tool (tout le reste est cassé) * Fix unconnected modify node * Fix freehand tool * Pen tool * Migrate demo art * Select points * Fix the demo artwork * Fix the X and Y inputs for path tool * G1 continous toggle * Delete points * Fix test * Insert point * Improve robustness of handles * Fix GRS shortcuts on path * Dragging points * Fix build * Preserve opposing handle lengths * Update demo art and snapping * Fix polygon tool * Double click end anchor * Improve dragging * Fix text shifting * Select only connected verts * Colinear alt * Cleanup * Fix imports * Improve pen tool avoiding handle placement * Improve disolve * Remove pivot widget from Transform node properties * Fix demo art * Fix bugs * Re-save demo artwork * Code review * Serialize hashmap as tuple vec to enable deserialize_inputs * Fix migrate * Add document upgrade function to editor_api.rs * Finalize document upgrading * Rename to the Path node * Remove smoothing from Freehand tool * Upgrade demo artwork * Propertly disable raw-rs tests --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> Co-authored-by: Adam <adamgerhant@gmail.com> Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
co-authored by
Keavon Chambers
Adam
Dennis Kobert
parent
fd3613018a
commit
1652c713a6
@@ -1,5 +1,6 @@
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::Message;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graphene_core::Color;
|
||||
use graphene_std::vector::style::FillChoice;
|
||||
@@ -58,6 +59,20 @@ impl ToolColorOptions {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_fill(&self, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
if let Some(color) = self.active_color() {
|
||||
let fill = graphene_core::vector::style::Fill::solid(color);
|
||||
responses.add(GraphOperationMessage::FillSet { layer, fill });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_stroke(&self, weight: f64, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
if let Some(color) = self.active_color() {
|
||||
let stroke = graphene_core::vector::style::Stroke::new(Some(color), weight);
|
||||
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_widgets(
|
||||
&self,
|
||||
label_text: impl Into<String>,
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::style::Gradient;
|
||||
use graphene_core::vector::PointId;
|
||||
use graphene_core::Color;
|
||||
|
||||
use glam::DVec2;
|
||||
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>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
pub fn new_vector_layer(subpaths: Vec<Subpath<PointId>>, id: NodeId, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
let insert_index = -1;
|
||||
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
|
||||
@@ -46,25 +44,16 @@ pub fn new_svg_layer(svg: String, transform: glam::DAffine2, id: NodeId, parent:
|
||||
});
|
||||
LayerNodeIdentifier::new_unchecked(id)
|
||||
}
|
||||
|
||||
/// Batch set all of the manipulator groups to set their colinear handle state on a specific layer
|
||||
pub fn set_manipulator_colinear_handles_state(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer: LayerNodeIdentifier, colinear: bool, responses: &mut VecDeque<Message>) {
|
||||
for manipulator_group in manipulator_groups {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator_group.id, colinear },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Locate the subpaths from the shape nodes of a particular layer
|
||||
pub fn get_subpaths(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<Subpath<ManipulatorGroupId>>> {
|
||||
let path_data_node_input_index = 0;
|
||||
if let TaggedValue::Subpaths(subpaths) = NodeGraphLayer::new(layer, document_network).find_input("Shape", path_data_node_input_index)? {
|
||||
Some(subpaths)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn new_custom(id: NodeId, nodes: HashMap<NodeId, DocumentNode>, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||
id,
|
||||
nodes,
|
||||
parent,
|
||||
insert_index: -1,
|
||||
alias: String::new(),
|
||||
});
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
|
||||
LayerNodeIdentifier::new_unchecked(id)
|
||||
}
|
||||
|
||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
||||
@@ -83,16 +72,6 @@ pub fn get_viewport_pivot(layer: LayerNodeIdentifier, document_network: &NodeNet
|
||||
document_metadata.transform_to_viewport(layer).transform_point2(min + (max - min) * pivot)
|
||||
}
|
||||
|
||||
/// Get the manipulator groups that currently have colinear handles for a particular layer from the shape node
|
||||
pub fn get_colinear_manipulators(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<&Vec<ManipulatorGroupId>> {
|
||||
let colinear_manipulators_node_input_index = 1;
|
||||
if let TaggedValue::ManipulatorGroupIds(manipulator_groups) = NodeGraphLayer::new(layer, document_network).find_input("Shape", colinear_manipulators_node_input_index)? {
|
||||
Some(manipulator_groups)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current gradient of a layer from the closest Fill node
|
||||
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
|
||||
let fill_index = 1;
|
||||
@@ -191,16 +170,6 @@ pub fn is_layer_fed_by_node_of_name(layer: LayerNodeIdentifier, document_network
|
||||
NodeGraphLayer::new(layer, document_network).find_node_inputs(node_name).is_some()
|
||||
}
|
||||
|
||||
/// Convert subpaths to an iterator of manipulator groups
|
||||
pub fn get_manipulator_groups(subpaths: &[Subpath<ManipulatorGroupId>]) -> impl Iterator<Item = &bezier_rs::ManipulatorGroup<ManipulatorGroupId>> + DoubleEndedIterator {
|
||||
subpaths.iter().flat_map(|subpath| subpath.manipulator_groups())
|
||||
}
|
||||
|
||||
/// Find a manipulator group with a specific id from several subpaths
|
||||
pub fn get_manipulator_from_id(subpaths: &[Subpath<ManipulatorGroupId>], id: ManipulatorGroupId) -> Option<&bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
|
||||
subpaths.iter().find_map(|subpath| subpath.manipulator_from_id(id))
|
||||
}
|
||||
|
||||
/// An immutable reference to a layer within the document node graph for easy access.
|
||||
pub struct NodeGraphLayer<'a> {
|
||||
node_graph: &'a NodeNetwork,
|
||||
|
||||
@@ -29,13 +29,12 @@ impl Resize {
|
||||
root_transform.transform_point2(self.drag_start)
|
||||
}
|
||||
|
||||
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
||||
let Some(layer) = self.layer else {
|
||||
return None;
|
||||
};
|
||||
pub fn calculate_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key) -> Option<[DVec2; 2]> {
|
||||
let layer = self.layer?;
|
||||
|
||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||
log::error!("Resize layer cannot be ROOT_PARENT");
|
||||
return None;
|
||||
}
|
||||
|
||||
if !document.network().nodes.contains_key(&layer.to_node()) {
|
||||
@@ -87,9 +86,14 @@ impl Resize {
|
||||
self.snap_manager.update_indicator(snapped);
|
||||
}
|
||||
|
||||
Some(points_viewport)
|
||||
}
|
||||
|
||||
pub fn calculate_transform(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key, skip_rerender: bool) -> Option<Message> {
|
||||
let points_viewport = self.calculate_points(document, input, center, lock_ratio)?;
|
||||
Some(
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer,
|
||||
layer: self.layer?,
|
||||
transform: DAffine2::from_scale_angle_translation(points_viewport[1] - points_viewport[0], 0., points_viewport[0]),
|
||||
transform_in: TransformIn::Viewport,
|
||||
skip_rerender,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,20 @@
|
||||
mod grid_snapper;
|
||||
mod layer_snapper;
|
||||
mod snap_results;
|
||||
pub use {grid_snapper::*, layer_snapper::*, snap_results::*};
|
||||
|
||||
use crate::consts::COLOR_OVERLAY_BLUE;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, GridSnapTarget, SnapTarget};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::{Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::cmp::Ordering;
|
||||
pub use {grid_snapper::*, layer_snapper::*, snap_results::*};
|
||||
|
||||
/// Handles snapping and snap overlays
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -150,7 +153,7 @@ pub struct SnapData<'a> {
|
||||
pub document: &'a DocumentMessageHandler,
|
||||
pub input: &'a InputPreprocessorMessageHandler,
|
||||
pub ignore: &'a [LayerNodeIdentifier],
|
||||
pub manipulators: Vec<(LayerNodeIdentifier, ManipulatorGroupId)>,
|
||||
pub manipulators: Vec<(LayerNodeIdentifier, PointId)>,
|
||||
pub candidates: Option<&'a Vec<LayerNodeIdentifier>>,
|
||||
}
|
||||
impl<'a> SnapData<'a> {
|
||||
@@ -172,7 +175,7 @@ impl<'a> SnapData<'a> {
|
||||
fn ignore_bounds(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.manipulators.iter().any(|&(ignore, _)| ignore == layer)
|
||||
}
|
||||
fn ignore_manipulator(&self, layer: LayerNodeIdentifier, manipulator: impl Into<ManipulatorGroupId>) -> bool {
|
||||
fn ignore_manipulator(&self, layer: LayerNodeIdentifier, manipulator: impl Into<PointId>) -> bool {
|
||||
self.manipulators.contains(&(layer, manipulator.into()))
|
||||
}
|
||||
}
|
||||
@@ -277,10 +280,8 @@ impl SnapManager {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(root) = snap_data.document.network.get_root_node() {
|
||||
if snap_data.document.network.nodes.get(&root.id).expect("Root should always be a node in find_candidates").is_layer {
|
||||
add_candidates(LayerNodeIdentifier::new(root.id, &snap_data.document.network), snap_data, quad, &mut candidates);
|
||||
}
|
||||
for layer in LayerNodeIdentifier::ROOT_PARENT.children(&document.metadata) {
|
||||
add_candidates(layer, snap_data, quad, &mut candidates);
|
||||
}
|
||||
|
||||
if candidates.len() > 10 {
|
||||
@@ -333,7 +334,7 @@ impl SnapManager {
|
||||
if let Some(ind) = &self.indicator {
|
||||
for curve in &ind.curves {
|
||||
let Some(curve) = curve else { continue };
|
||||
overlay_context.outline::<ManipulatorGroupId>([Subpath::from_bezier(curve)].iter(), to_viewport);
|
||||
overlay_context.outline([Subpath::from_bezier(curve)].iter(), to_viewport);
|
||||
}
|
||||
if let Some(quad) = ind.target_bounds {
|
||||
overlay_context.quad(to_viewport * quad);
|
||||
|
||||
@@ -8,8 +8,7 @@ use crate::messages::prelude::*;
|
||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LayerSnapper {
|
||||
@@ -33,7 +32,7 @@ impl LayerSnapper {
|
||||
self.paths_to_snap.push(SnapCandidatePath {
|
||||
document_curve,
|
||||
layer,
|
||||
start: ManipulatorGroupId::new(),
|
||||
start: PointId::new(),
|
||||
target,
|
||||
bounds: Some(bounds),
|
||||
});
|
||||
@@ -133,7 +132,7 @@ impl LayerSnapper {
|
||||
let direction = constraint.direction().normalize_or_zero();
|
||||
let start = constrained_point - tolerance * direction;
|
||||
let end = constrained_point + tolerance * direction;
|
||||
Subpath::<ManipulatorGroupId>::new_line(start, end)
|
||||
Subpath::<PointId>::new_line(start, end)
|
||||
};
|
||||
|
||||
for path in &self.paths_to_snap {
|
||||
@@ -296,7 +295,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
||||
struct SnapCandidatePath {
|
||||
document_curve: Bezier,
|
||||
layer: LayerNodeIdentifier,
|
||||
start: ManipulatorGroupId,
|
||||
start: PointId,
|
||||
target: SnapTarget,
|
||||
bounds: Option<Quad>,
|
||||
}
|
||||
@@ -418,8 +417,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if both handles in a manipulator group are colinear, unless the anchor is an endpoint. Endpoint anchors are never considered colinear.
|
||||
pub fn are_manipulator_handles_colinear<Id: bezier_rs::Identifier>(group: &bezier_rs::ManipulatorGroup<Id>, to_document: DAffine2, subpath: &Subpath<Id>, index: usize) -> bool {
|
||||
pub fn are_manipulator_handles_colinear(group: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
let anchor = group.anchor;
|
||||
let handle_in = group.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = group.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::misc::{SnapSource, Snap
|
||||
use bezier_rs::Bezier;
|
||||
use glam::DVec2;
|
||||
use graphene_core::renderer::Quad;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::PointId;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SnapResults {
|
||||
@@ -79,7 +79,7 @@ pub struct SnappedLine {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SnappedCurve {
|
||||
pub layer: LayerNodeIdentifier,
|
||||
pub start: ManipulatorGroupId,
|
||||
pub start: PointId,
|
||||
pub point: SnappedPoint,
|
||||
pub document_curve: Bezier,
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ impl SelectedEdges {
|
||||
for point in points {
|
||||
let old_position = point.document_point;
|
||||
let bounds_space = bounds_to_doc.inverse().transform_point2(point.document_point);
|
||||
let normalised = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]);
|
||||
let updated = normalised * (max - min) + min;
|
||||
let normalized = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]);
|
||||
let updated = normalized * (max - min) + min;
|
||||
point.document_point = bounds_to_doc.transform_point2(updated);
|
||||
let mut snapped = if constrain {
|
||||
let constraint = SnapConstraint::Line {
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
||||
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
use glam::DVec2;
|
||||
|
||||
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
|
||||
pub fn should_extend(document: &DocumentMessageHandler, pos: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, usize, bool)> {
|
||||
pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
|
||||
let mut best = None;
|
||||
let mut best_distance_squared = tolerance * tolerance;
|
||||
|
||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||
let viewspace = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
let subpaths = get_subpaths(layer, &document.network)?;
|
||||
for (subpath_index, subpath) in subpaths.iter().enumerate() {
|
||||
if subpath.closed() {
|
||||
continue;
|
||||
}
|
||||
let vector_data = document.metadata.compute_modified_vector(layer, document.network())?;
|
||||
for id in vector_data.single_connected_points() {
|
||||
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||
|
||||
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(point).distance_squared(goal);
|
||||
|
||||
let distance_squared = viewspace.transform_point2(manipulator_group.anchor).distance_squared(pos);
|
||||
|
||||
if distance_squared < best_distance_squared {
|
||||
best = Some((layer, subpath_index, from_start));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
if distance_squared < best_distance_squared {
|
||||
best = Some((layer, id, point));
|
||||
best_distance_squared = distance_squared;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user