mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 01:58:11 +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
co-authored by
Keavon Chambers
parent
639a24d8ad
commit
959e790cdf
@@ -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; )
|
||||
}
|
||||
}
|
||||
+301
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -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()),
|
||||
|
||||
+39
-16
@@ -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 {
|
||||
|
||||
+4
-4
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user