mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 17:18: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
Generated
+2
@@ -2392,6 +2392,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
"specta",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
"usvg",
|
"usvg",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
@@ -2415,6 +2416,7 @@ name = "graphite-wasm"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
"bezier-rs",
|
||||||
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
"graphite-editor",
|
"graphite-editor",
|
||||||
|
|||||||
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -64,3 +64,10 @@ web-sys = { workspace = true, features = [
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
# TODO: figure out why we check these features when they do not exist
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = [
|
||||||
|
'cfg(feature, values("resvg", "vello"))',
|
||||||
|
] }
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ impl Editor {
|
|||||||
std::mem::take(&mut self.dispatcher.responses)
|
std::mem::take(&mut self.dispatcher.responses)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
self.dispatcher.poll_node_graph_evaluation(responses);
|
self.dispatcher.poll_node_graph_evaluation(responses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
|
|||||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
pub const SELECTION_THRESHOLD: f64 = 10.;
|
||||||
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||||
pub const INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE: f64 = 50.;
|
pub const INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE: f64 = 50.;
|
||||||
pub const INSERT_POINT_ON_SEGMENT_TOO_CLOSE_DISTANCE: f64 = 5.;
|
|
||||||
|
|
||||||
// Pen tool
|
// Pen tool
|
||||||
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ impl Dispatcher {
|
|||||||
list
|
list
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses);
|
self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the tree structure for logging the messages as a tree
|
/// Create the tree structure for logging the messages as a tree
|
||||||
@@ -262,10 +262,7 @@ mod test {
|
|||||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::ToolType;
|
|
||||||
use crate::test_utils::EditorTestUtils;
|
use crate::test_utils::EditorTestUtils;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
|
|
||||||
fn init_logger() {
|
fn init_logger() {
|
||||||
@@ -412,11 +409,9 @@ mod test {
|
|||||||
assert_eq!(layers_after_copy[5], shape_id);
|
assert_eq!(layers_after_copy[5], shape_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fix text
|
#[tokio::test]
|
||||||
#[ignore]
|
|
||||||
#[test]
|
|
||||||
/// This test will fail when you make changes to the underlying serialization format for a document.
|
/// This test will fail when you make changes to the underlying serialization format for a document.
|
||||||
fn check_if_demo_art_opens() {
|
async fn check_if_demo_art_opens() {
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
|
||||||
let print_problem_to_terminal_on_failure = |value: &String| {
|
let print_problem_to_terminal_on_failure = |value: &String| {
|
||||||
@@ -449,6 +444,16 @@ mod test {
|
|||||||
document_name: document_name.into(),
|
document_name: document_name.into(),
|
||||||
document_serialized_content,
|
document_serialized_content,
|
||||||
});
|
});
|
||||||
|
println!("Responses:\n{responses:#?}");
|
||||||
|
|
||||||
|
// Check if the graph renders
|
||||||
|
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||||
|
portfolio
|
||||||
|
.executor
|
||||||
|
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id.unwrap()).unwrap(), glam::UVec2::ONE);
|
||||||
|
crate::node_graph_executor::run_node_graph().await;
|
||||||
|
let mut messages = VecDeque::new();
|
||||||
|
editor.poll_node_graph_evaluation(&mut messages).expect("Graph should render");
|
||||||
|
|
||||||
for response in responses {
|
for response in responses {
|
||||||
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
// Check for the existence of the file format incompatibility warning dialog after opening the test file
|
||||||
|
|||||||
@@ -99,6 +99,19 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "copyText")]
|
#[serde(rename = "copyText")]
|
||||||
copy_text: String,
|
copy_text: String,
|
||||||
},
|
},
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
TriggerUpgradeDocumentToVectorManipulationFormat {
|
||||||
|
#[serde(rename = "documentId")]
|
||||||
|
document_id: DocumentId,
|
||||||
|
#[serde(rename = "documentName")]
|
||||||
|
document_name: String,
|
||||||
|
#[serde(rename = "documentIsAutoSaved")]
|
||||||
|
document_is_auto_saved: bool,
|
||||||
|
#[serde(rename = "documentIsSaved")]
|
||||||
|
document_is_saved: bool,
|
||||||
|
#[serde(rename = "documentSerializedContent")]
|
||||||
|
document_serialized_content: String,
|
||||||
|
},
|
||||||
TriggerViewportResize,
|
TriggerViewportResize,
|
||||||
TriggerVisitLink {
|
TriggerVisitLink {
|
||||||
url: String,
|
url: String,
|
||||||
|
|||||||
@@ -1236,32 +1236,27 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find any layers sorted by index that are under the given location in viewport space.
|
/// Find any layers sorted by index that are under the given location in viewport space.
|
||||||
pub fn click_list_any(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
|
pub fn click_xray_no_artboards<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||||
self.click_xray(viewport_location).filter(|&layer| !is_artboard(layer, network)).collect::<Vec<_>>()
|
self.click_xray(viewport_location).filter(move |&layer| !is_artboard(layer, network))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
/// Find layers under the location in viewport space that was clicked, listed by their depth in the layer tree hierarchy.
|
||||||
pub fn click_list(&self, viewport_location: DVec2, network: &NodeNetwork) -> Vec<LayerNodeIdentifier> {
|
pub fn click_list<'a>(&'a self, viewport_location: DVec2, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||||
let mut node_list = self.click_list_any(viewport_location, network);
|
self.click_xray_no_artboards(viewport_location, network)
|
||||||
node_list.truncate(
|
.skip_while(|&layer| layer == LayerNodeIdentifier::ROOT_PARENT)
|
||||||
node_list
|
.scan(true, |last_had_children, layer| {
|
||||||
.iter()
|
if *last_had_children {
|
||||||
.position(|&layer| {
|
*last_had_children = network.nodes.get(&layer.to_node()).map_or(false, |node| node.layer_has_child_layers(network));
|
||||||
if layer != LayerNodeIdentifier::ROOT_PARENT {
|
Some(layer)
|
||||||
!network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default()
|
} else {
|
||||||
} else {
|
None
|
||||||
log::error!("ROOT_PARENT should not exist in click_list_any");
|
}
|
||||||
false
|
})
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or(0) + 1,
|
|
||||||
);
|
|
||||||
node_list
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
||||||
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
|
pub fn click(&self, viewport_location: DVec2, network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
|
||||||
self.click_list(viewport_location, network).last().copied()
|
self.click_list(viewport_location, network).last()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
|
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use super::utility_types::TransformIn;
|
use super::utility_types::TransformIn;
|
||||||
use super::utility_types::VectorDataModification;
|
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
@@ -7,9 +6,10 @@ use bezier_rs::Subpath;
|
|||||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
|
||||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
use graphene_core::vector::style::{Fill, Stroke};
|
||||||
|
use graphene_core::vector::PointId;
|
||||||
|
use graphene_core::vector::VectorModificationType;
|
||||||
use graphene_core::{Artboard, Color};
|
use graphene_core::{Artboard, Color};
|
||||||
use graphene_std::vector::misc::BooleanOperation;
|
use graphene_std::vector::misc::BooleanOperation;
|
||||||
|
|
||||||
@@ -77,11 +77,6 @@ pub enum GraphOperationMessage {
|
|||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
blend_mode: BlendMode,
|
blend_mode: BlendMode,
|
||||||
},
|
},
|
||||||
UpdateBounds {
|
|
||||||
layer: LayerNodeIdentifier,
|
|
||||||
old_bounds: [DVec2; 2],
|
|
||||||
new_bounds: [DVec2; 2],
|
|
||||||
},
|
|
||||||
StrokeSet {
|
StrokeSet {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
stroke: Stroke,
|
stroke: Stroke,
|
||||||
@@ -104,7 +99,7 @@ pub enum GraphOperationMessage {
|
|||||||
},
|
},
|
||||||
Vector {
|
Vector {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
modification: VectorDataModification,
|
modification_type: VectorModificationType,
|
||||||
},
|
},
|
||||||
Brush {
|
Brush {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
@@ -129,7 +124,7 @@ pub enum GraphOperationMessage {
|
|||||||
},
|
},
|
||||||
NewVectorLayer {
|
NewVectorLayer {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
subpaths: Vec<Subpath<ManipulatorGroupId>>,
|
subpaths: Vec<Subpath<PointId>>,
|
||||||
parent: LayerNodeIdentifier,
|
parent: LayerNodeIdentifier,
|
||||||
insert_index: isize,
|
insert_index: isize,
|
||||||
},
|
},
|
||||||
|
|||||||
+9
-26
@@ -1,4 +1,4 @@
|
|||||||
use super::transform_utils::{self, LayerBounds};
|
use super::transform_utils;
|
||||||
use super::utility_types::ModifyInputsContext;
|
use super::utility_types::ModifyInputsContext;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
@@ -9,10 +9,9 @@ use graph_craft::document::value::TaggedValue;
|
|||||||
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
|
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
|
use graphene_core::vector::style::{Fill, Gradient, GradientStops, GradientType, LineCap, LineJoin, Stroke};
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
use graphene_std::vector::convert_usvg_path;
|
use graphene_std::vector::convert_usvg_path;
|
||||||
use graphene_std::vector::style::GradientStops;
|
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
|
|
||||||
@@ -404,15 +403,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
modify_inputs.blend_mode_set(blend_mode);
|
modify_inputs.blend_mode_set(blend_mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
|
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
|
||||||
log::error!("Cannot run UpdateBounds on ROOT_PARENT");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
|
||||||
modify_inputs.update_bounds(old_bounds, new_bounds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
GraphOperationMessage::StrokeSet { layer, stroke } => {
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
log::error!("Cannot run StrokeSet on ROOT_PARENT");
|
log::error!("Cannot run StrokeSet on ROOT_PARENT");
|
||||||
@@ -433,9 +423,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||||
let bounds = LayerBounds::new(document_metadata, layer);
|
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||||
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
|
modify_inputs.transform_change(transform, transform_in, parent_transform, skip_rerender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::TransformSet {
|
GraphOperationMessage::TransformSet {
|
||||||
@@ -451,9 +440,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
|
||||||
|
|
||||||
let current_transform = Some(document_metadata.transform_to_viewport(layer));
|
let current_transform = Some(document_metadata.transform_to_viewport(layer));
|
||||||
let bounds = LayerBounds::new(document_metadata, layer);
|
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
|
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, skip_rerender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
|
||||||
@@ -461,21 +449,17 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let bounds = LayerBounds::new(document_metadata, layer);
|
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||||
modify_inputs.pivot_set(pivot, bounds);
|
modify_inputs.pivot_set(pivot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::Vector { layer, modification } => {
|
GraphOperationMessage::Vector { layer, modification_type } => {
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
log::error!("Cannot run Vector on ROOT_PARENT");
|
log::error!("Cannot run Vector on ROOT_PARENT");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
|
||||||
let previous_layer = modify_inputs.vector_modify(modification);
|
modify_inputs.vector_modify(modification_type);
|
||||||
if let Some(layer) = previous_layer {
|
|
||||||
responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphOperationMessage::Brush { layer, strokes } => {
|
GraphOperationMessage::Brush { layer, strokes } => {
|
||||||
@@ -698,6 +682,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
GraphOperationMessage::SetName { layer, name } => {
|
GraphOperationMessage::SetName { layer, name } => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
|
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
}
|
}
|
||||||
GraphOperationMessage::SetNameImpl { layer, name } => {
|
GraphOperationMessage::SetNameImpl { layer, name } => {
|
||||||
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
|
if let Some(node) = document_network.nodes.get_mut(&layer.to_node()) {
|
||||||
@@ -829,10 +814,8 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
modify_inputs.insert_vector_data(subpaths, layer);
|
modify_inputs.insert_vector_data(subpaths, layer);
|
||||||
|
|
||||||
let center = DAffine2::from_translation((bounds[0] + bounds[1]) / 2.);
|
|
||||||
|
|
||||||
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
|
modify_inputs.modify_inputs("Transform", true, |inputs, _node_id, _metadata| {
|
||||||
transform_utils::update_transform(inputs, center.inverse() * transform * usvg_transform(node.abs_transform()) * center);
|
transform_utils::update_transform(inputs, transform * usvg_transform(node.abs_transform()));
|
||||||
});
|
});
|
||||||
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
let bounds_transform = DAffine2::from_scale_angle_translation(bounds[1] - bounds[0], 0., bounds[0]);
|
||||||
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
let transformed_bound_transform = DAffine2::from_scale_angle_translation(transformed_bounds[1] - transformed_bounds[0], 0., transformed_bounds[0]);
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use bezier_rs::Subpath;
|
||||||
|
|
||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
|
||||||
use graph_craft::document::{value::TaggedValue, NodeInput};
|
use graph_craft::document::{value::TaggedValue, NodeInput};
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::vector::PointId;
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
use super::utility_types::VectorDataModification;
|
|
||||||
|
|
||||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||||
pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {
|
pub fn compute_scale_angle_translation_shear(transform: DAffine2) -> (DVec2, f64, DVec2, DVec2) {
|
||||||
let x_axis = transform.matrix2.x_axis;
|
let x_axis = transform.matrix2.x_axis;
|
||||||
@@ -53,7 +48,10 @@ pub struct LayerBounds {
|
|||||||
|
|
||||||
impl LayerBounds {
|
impl LayerBounds {
|
||||||
/// Extract the layer bounds and their transform for a layer.
|
/// Extract the layer bounds and their transform for a layer.
|
||||||
pub fn new(metadata: &DocumentMetadata, layer: LayerNodeIdentifier) -> Self {
|
pub fn new(
|
||||||
|
metadata: &crate::messages::portfolio::document::utility_types::document_metadata::DocumentMetadata,
|
||||||
|
layer: crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bounds: metadata.nonzero_bounding_box(layer),
|
bounds: metadata.nonzero_bounding_box(layer),
|
||||||
bounds_transform: DAffine2::IDENTITY,
|
bounds_transform: DAffine2::IDENTITY,
|
||||||
@@ -184,7 +182,7 @@ fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
|||||||
[bounds_min, bounds_max]
|
[bounds_min, bounds_max]
|
||||||
}
|
}
|
||||||
/// Returns corners of all subpaths
|
/// Returns corners of all subpaths
|
||||||
fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||||
subpaths
|
subpaths
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|subpath| subpath.bounding_box())
|
.filter_map(|subpath| subpath.bounding_box())
|
||||||
@@ -193,116 +191,7 @@ fn subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<ManipulatorGroupId>]) -> [DVec2; 2] {
|
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||||
clamp_bounds(bounds_min, bounds_max)
|
clamp_bounds(bounds_min, bounds_max)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VectorModificationState<'a> {
|
|
||||||
pub subpaths: &'a mut Vec<Subpath<ManipulatorGroupId>>,
|
|
||||||
pub colinear_manipulators: &'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_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId, colinear: bool) {
|
|
||||||
if !colinear {
|
|
||||||
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
|
||||||
} else if !self.colinear_manipulators.contains(&id) {
|
|
||||||
self.colinear_manipulators.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_manipulator_colinear_handles_state(&mut self, id: ManipulatorGroupId) {
|
|
||||||
if self.colinear_manipulators.contains(&id) {
|
|
||||||
self.colinear_manipulators.retain(|&manipulator_group_id| manipulator_group_id != id);
|
|
||||||
} else {
|
|
||||||
self.colinear_manipulators.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_position(&mut self, point: ManipulatorPointId, position: DVec2) {
|
|
||||||
assert!(position.is_finite(), "Point position should be finite");
|
|
||||||
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.colinear_manipulators.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::SetManipulatorColinearHandlesState { id, colinear } => self.set_manipulator_colinear_handles_state(id, colinear),
|
|
||||||
VectorDataModification::SetManipulatorPosition { point, position } => self.set_position(point, position),
|
|
||||||
VectorDataModification::ToggleManipulatorColinearHandlesState { id } => self.toggle_manipulator_colinear_handles_state(id),
|
|
||||||
VectorDataModification::UpdateSubpaths { subpaths } => *self.subpaths = subpaths,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::transform_utils::{self, LayerBounds};
|
use super::transform_utils;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||||
@@ -10,12 +10,10 @@ use graph_craft::document::value::TaggedValue;
|
|||||||
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, Previewing};
|
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, Previewing};
|
||||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
|
||||||
use graphene_core::vector::brush_stroke::BrushStroke;
|
use graphene_core::vector::brush_stroke::BrushStroke;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
use graphene_core::vector::style::{Fill, Stroke};
|
||||||
use graphene_core::Type;
|
use graphene_core::vector::{PointId, VectorModificationType};
|
||||||
use graphene_core::{Artboard, Color};
|
use graphene_core::{Artboard, Color, Type};
|
||||||
use graphene_std::vector::ManipulatorPointId;
|
|
||||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||||
|
|
||||||
@@ -29,22 +27,6 @@ pub enum TransformIn {
|
|||||||
Viewport,
|
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 },
|
|
||||||
SetManipulatorColinearHandlesState { id: ManipulatorGroupId, colinear: bool },
|
|
||||||
SetManipulatorPosition { point: ManipulatorPointId, position: DVec2 },
|
|
||||||
ToggleManipulatorColinearHandlesState { id: ManipulatorGroupId },
|
|
||||||
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is helpful to prevent passing the same arguments to multiple functions, but is currently inefficient due to the collect_outwards_wires. Move it into a function and use only when needed.
|
// TODO: This is helpful to prevent passing the same arguments to multiple functions, but is currently inefficient due to the collect_outwards_wires. Move it into a function and use only when needed.
|
||||||
/// NodeGraphMessage or GraphOperationMessage cannot be added in ModifyInputsContext, since the functions are called by both messages handlers
|
/// NodeGraphMessage or GraphOperationMessage cannot be added in ModifyInputsContext, since the functions are called by both messages handlers
|
||||||
pub struct ModifyInputsContext<'a> {
|
pub struct ModifyInputsContext<'a> {
|
||||||
@@ -315,7 +297,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
ModifyInputsContext::insert_node_as_primary_export(node_graph, document_network, new_id, artboard_node)
|
ModifyInputsContext::insert_node_as_primary_export(node_graph, document_network, new_id, artboard_node)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
|
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: NodeId) {
|
||||||
let shape = {
|
let shape = {
|
||||||
let node_type: &crate::messages::portfolio::document::node_graph::document_node_types::DocumentNodeDefinition = resolve_document_node_type("Shape").expect("Shape node does not exist");
|
let node_type: &crate::messages::portfolio::document::node_graph::document_node_types::DocumentNodeDefinition = resolve_document_node_type("Shape").expect("Shape node does not exist");
|
||||||
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
|
node_type.to_document_node_default_inputs([Some(NodeInput::value(TaggedValue::Subpaths(subpaths), false))], Default::default())
|
||||||
@@ -453,10 +435,9 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the inputs of a specific node
|
/// Find a node id as part of the layer
|
||||||
pub fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
fn existing_node_id(&mut self, name: &'static str) -> Option<NodeId> {
|
||||||
let existing_node_id = self
|
self.document_network
|
||||||
.document_network
|
|
||||||
.upstream_flow_back_from_nodes(
|
.upstream_flow_back_from_nodes(
|
||||||
self.layer_node.map_or_else(
|
self.layer_node.map_or_else(
|
||||||
|| {
|
|| {
|
||||||
@@ -471,7 +452,20 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
graph_craft::document::FlowType::HorizontalFlow,
|
graph_craft::document::FlowType::HorizontalFlow,
|
||||||
)
|
)
|
||||||
.find(|(node, _)| node.name == name)
|
.find(|(node, _)| node.name == name)
|
||||||
.map(|(_, id)| id);
|
.map(|(_, id)| id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the input of a specific node; skipping if it doesn't exist
|
||||||
|
pub fn modify_existing_inputs(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||||
|
let existing_node_id = self.existing_node_id(name);
|
||||||
|
if let Some(node_id) = existing_node_id {
|
||||||
|
self.modify_existing_node_inputs(node_id, update_input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the inputs of a specific node; creating it if it doesn't exist
|
||||||
|
pub fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
|
||||||
|
let existing_node_id = self.existing_node_id(name);
|
||||||
if let Some(node_id) = existing_node_id {
|
if let Some(node_id) = existing_node_id {
|
||||||
self.modify_existing_node_inputs(node_id, update_input);
|
self.modify_existing_node_inputs(node_id, update_input);
|
||||||
} else {
|
} else {
|
||||||
@@ -625,22 +619,20 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, bounds: LayerBounds, skip_rerender: bool) {
|
pub fn transform_change(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
|
||||||
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
self.modify_inputs("Transform", skip_rerender, |inputs, _node_id, _metadata| {
|
||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
let layer_transform = transform_utils::get_current_transform(inputs);
|
||||||
let upstream_transform = metadata.upstream_transform(node_id);
|
|
||||||
let to = match transform_in {
|
let to = match transform_in {
|
||||||
TransformIn::Local => DAffine2::IDENTITY,
|
TransformIn::Local => DAffine2::IDENTITY,
|
||||||
TransformIn::Scope { scope } => scope * parent_transform,
|
TransformIn::Scope { scope } => scope * parent_transform,
|
||||||
TransformIn::Viewport => parent_transform,
|
TransformIn::Viewport => parent_transform,
|
||||||
};
|
};
|
||||||
let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
let transform = to.inverse() * transform * to * layer_transform;
|
||||||
let transform = pivot.inverse() * to.inverse() * transform * to * pivot * layer_transform;
|
|
||||||
transform_utils::update_transform(inputs, transform);
|
transform_utils::update_transform(inputs, transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, bounds: LayerBounds, skip_rerender: bool) {
|
pub fn transform_set(&mut self, mut transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, current_transform: Option<DAffine2>, skip_rerender: bool) {
|
||||||
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
self.modify_inputs("Transform", skip_rerender, |inputs, node_id, metadata| {
|
||||||
let upstream_transform = metadata.upstream_transform(node_id);
|
let upstream_transform = metadata.upstream_transform(node_id);
|
||||||
|
|
||||||
@@ -649,7 +641,6 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
TransformIn::Scope { scope } => scope * parent_transform,
|
TransformIn::Scope { scope } => scope * parent_transform,
|
||||||
TransformIn::Viewport => parent_transform,
|
TransformIn::Viewport => parent_transform,
|
||||||
};
|
};
|
||||||
let pivot = DAffine2::from_translation(upstream_transform.transform_point2(bounds.layerspace_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
|
||||||
|
|
||||||
if current_transform
|
if current_transform
|
||||||
.filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.)
|
.filter(|transform| transform.matrix2.determinant() != 0. && upstream_transform.matrix2.determinant() != 0.)
|
||||||
@@ -657,79 +648,29 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
{
|
{
|
||||||
transform *= upstream_transform.inverse();
|
transform *= upstream_transform.inverse();
|
||||||
}
|
}
|
||||||
let final_transform = pivot.inverse() * to.inverse() * transform * pivot;
|
let final_transform = to.inverse() * transform;
|
||||||
transform_utils::update_transform(inputs, final_transform);
|
transform_utils::update_transform(inputs, final_transform);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pivot_set(&mut self, new_pivot: DVec2, bounds: LayerBounds) {
|
pub fn pivot_set(&mut self, new_pivot: DVec2) {
|
||||||
self.modify_inputs("Transform", false, |inputs, node_id, metadata| {
|
self.modify_inputs("Transform", false, |inputs, _node_id, _metadata| {
|
||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
|
||||||
let upstream_transform = metadata.upstream_transform(node_id);
|
|
||||||
let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(transform_utils::get_current_normalized_pivot(inputs))));
|
|
||||||
let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(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);
|
inputs[5] = NodeInput::value(TaggedValue::DVec2(new_pivot), false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_bounds(&mut self, [old_bounds_min, old_bounds_max]: [DVec2; 2], [new_bounds_min, new_bounds_max]: [DVec2; 2]) {
|
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
|
||||||
self.modify_all_node_inputs("Transform", false, |inputs, node_id, metadata| {
|
self.modify_inputs("Path", false, |inputs, _node_id, _metadata| {
|
||||||
let upstream_transform = metadata.upstream_transform(node_id);
|
let [_, NodeInput::Value {
|
||||||
let layer_transform = transform_utils::get_current_transform(inputs);
|
tagged_value: TaggedValue::VectorModification(modification),
|
||||||
let normalized_pivot = transform_utils::get_current_normalized_pivot(inputs);
|
|
||||||
|
|
||||||
let old_layerspace_pivot = (old_bounds_max - old_bounds_min) * normalized_pivot + old_bounds_min;
|
|
||||||
let new_layerspace_pivot = (new_bounds_max - new_bounds_min) * normalized_pivot + new_bounds_min;
|
|
||||||
let new_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(new_layerspace_pivot));
|
|
||||||
let old_pivot_transform = DAffine2::from_translation(upstream_transform.transform_point2(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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vector_modify(&mut self, modification: VectorDataModification) -> Option<LayerNodeIdentifier> {
|
|
||||||
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];
|
|
||||||
let mut empty = false;
|
|
||||||
|
|
||||||
self.modify_inputs("Shape", false, |inputs, _node_id, _metadata| {
|
|
||||||
let [subpaths, colinear_manipulators] = inputs.as_mut_slice() else {
|
|
||||||
panic!("Shape does not have both `subpath` and `colinear_manipulators` inputs");
|
|
||||||
};
|
|
||||||
|
|
||||||
let NodeInput::Value {
|
|
||||||
tagged_value: TaggedValue::Subpaths(subpaths),
|
|
||||||
..
|
..
|
||||||
} = subpaths
|
}] = inputs.as_mut_slice()
|
||||||
else {
|
else {
|
||||||
return;
|
panic!("Path node does not have modification input");
|
||||||
};
|
|
||||||
let NodeInput::Value {
|
|
||||||
tagged_value: TaggedValue::ManipulatorGroupIds(colinear_manipulators),
|
|
||||||
..
|
|
||||||
} = colinear_manipulators
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[old_bounds_min, old_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
modification.modify(&modification_type);
|
||||||
|
|
||||||
transform_utils::VectorModificationState { subpaths, colinear_manipulators }.modify(modification);
|
|
||||||
empty = !subpaths.iter().any(|subpath| !subpath.is_empty());
|
|
||||||
|
|
||||||
[new_bounds_min, new_bounds_max] = transform_utils::nonzero_subpath_bounds(subpaths);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
|
|
||||||
|
|
||||||
if empty {
|
|
||||||
self.layer_node.map(|layer_id| LayerNodeIdentifier::new(layer_id, &self.document_network))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
|
||||||
|
|||||||
@@ -761,20 +761,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentInputType::value("Dimensions", TaggedValue::UVec2((512, 512).into()), false),
|
DocumentInputType::value("Dimensions", TaggedValue::UVec2((512, 512).into()), false),
|
||||||
DocumentInputType::value("Seed", TaggedValue::U32(0), false),
|
DocumentInputType::value("Seed", TaggedValue::U32(0), false),
|
||||||
DocumentInputType::value("Scale", TaggedValue::F64(10.), false),
|
DocumentInputType::value("Scale", TaggedValue::F64(10.), false),
|
||||||
DocumentInputType::value("Noise Type", TaggedValue::NoiseType(NoiseType::Perlin), false),
|
DocumentInputType::value("Noise Type", TaggedValue::NoiseType(NoiseType::default()), false),
|
||||||
// Domain Warp
|
// Domain Warp
|
||||||
DocumentInputType::value("Domain Warp Type", TaggedValue::DomainWarpType(DomainWarpType::None), false),
|
DocumentInputType::value("Domain Warp Type", TaggedValue::DomainWarpType(DomainWarpType::default()), false),
|
||||||
DocumentInputType::value("Domain Warp Amplitude", TaggedValue::F64(100.), false),
|
DocumentInputType::value("Domain Warp Amplitude", TaggedValue::F64(100.), false),
|
||||||
// Fractal
|
// Fractal
|
||||||
DocumentInputType::value("Fractal Type", TaggedValue::FractalType(FractalType::None), false),
|
DocumentInputType::value("Fractal Type", TaggedValue::FractalType(FractalType::default()), false),
|
||||||
DocumentInputType::value("Fractal Octaves", TaggedValue::U32(3), false),
|
DocumentInputType::value("Fractal Octaves", TaggedValue::U32(3), false),
|
||||||
DocumentInputType::value("Fractal Lacunarity", TaggedValue::F64(2.), false),
|
DocumentInputType::value("Fractal Lacunarity", TaggedValue::F64(2.), false),
|
||||||
DocumentInputType::value("Fractal Gain", TaggedValue::F64(0.5), false),
|
DocumentInputType::value("Fractal Gain", TaggedValue::F64(0.5), false),
|
||||||
DocumentInputType::value("Fractal Weighted Strength", TaggedValue::F64(0.), false), // 0-1 range
|
DocumentInputType::value("Fractal Weighted Strength", TaggedValue::F64(0.), false), // 0-1 range
|
||||||
DocumentInputType::value("Fractal Ping Pong Strength", TaggedValue::F64(2.), false),
|
DocumentInputType::value("Fractal Ping Pong Strength", TaggedValue::F64(2.), false),
|
||||||
// Cellular
|
// Cellular
|
||||||
DocumentInputType::value("Cellular Distance Function", TaggedValue::CellularDistanceFunction(CellularDistanceFunction::Euclidean), false),
|
DocumentInputType::value("Cellular Distance Function", TaggedValue::CellularDistanceFunction(CellularDistanceFunction::default()), false),
|
||||||
DocumentInputType::value("Cellular Return Type", TaggedValue::CellularReturnType(CellularReturnType::Nearest), false),
|
DocumentInputType::value("Cellular Return Type", TaggedValue::CellularReturnType(CellularReturnType::default()), false),
|
||||||
DocumentInputType::value("Cellular Jitter", TaggedValue::F64(1.), false),
|
DocumentInputType::value("Cellular Jitter", TaggedValue::F64(1.), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
@@ -802,7 +802,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
DocumentInputType::value("Insertion", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
DocumentInputType::value("Insertion", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
DocumentInputType::value("Replace", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
DocumentInputType::value("Replace", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::insert_channel_properties,
|
properties: node_properties::insert_channel_properties,
|
||||||
@@ -935,7 +935,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
name: "Color Channel",
|
name: "Color Channel",
|
||||||
category: "Raster",
|
category: "Raster",
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
||||||
inputs: vec![DocumentInputType::value("Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false)],
|
inputs: vec![DocumentInputType::value("Channel", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false)],
|
||||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
||||||
properties: node_properties::color_channel_properties,
|
properties: node_properties::color_channel_properties,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -944,7 +944,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
name: "Blend Mode Value",
|
name: "Blend Mode Value",
|
||||||
category: "Inputs",
|
category: "Inputs",
|
||||||
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"),
|
||||||
inputs: vec![DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::Normal), false)],
|
inputs: vec![DocumentInputType::value("Blend Mode", TaggedValue::BlendMode(BlendMode::default()), false)],
|
||||||
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
||||||
properties: node_properties::blend_mode_value_properties,
|
properties: node_properties::blend_mode_value_properties,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -955,7 +955,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::raster::LuminanceNode<_>"),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
DocumentInputType::value("Luminance Calc", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
DocumentInputType::value("Luminance Calc", TaggedValue::LuminanceCalculation(LuminanceCalculation::default()), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::luminance_properties,
|
properties: node_properties::luminance_properties,
|
||||||
@@ -967,7 +967,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExtractChannelNode<_>"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::raster::ExtractChannelNode<_>"),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
DocumentInputType::value("From", TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
DocumentInputType::value("From", TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::default()), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::extract_channel_properties,
|
properties: node_properties::extract_channel_properties,
|
||||||
@@ -1864,7 +1864,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentInputType::value("(Blue) Blue", TaggedValue::F64(100.), false),
|
DocumentInputType::value("(Blue) Blue", TaggedValue::F64(100.), false),
|
||||||
DocumentInputType::value("(Blue) Constant", TaggedValue::F64(0.), false),
|
DocumentInputType::value("(Blue) Constant", TaggedValue::F64(0.), false),
|
||||||
// Display-only properties (not used within the node)
|
// Display-only properties (not used within the node)
|
||||||
DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::Red), false),
|
DocumentInputType::value("Output Channel", TaggedValue::RedGreenBlue(RedGreenBlue::default()), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::adjust_channel_mixer_properties,
|
properties: node_properties::adjust_channel_mixer_properties,
|
||||||
@@ -1879,7 +1879,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
DocumentInputType::value("Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||||
// Mode
|
// Mode
|
||||||
DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), false),
|
DocumentInputType::value("Mode", TaggedValue::RelativeAbsolute(RelativeAbsolute::default()), false),
|
||||||
// Reds
|
// Reds
|
||||||
DocumentInputType::value("(Reds) Cyan", TaggedValue::F64(0.), false),
|
DocumentInputType::value("(Reds) Cyan", TaggedValue::F64(0.), false),
|
||||||
DocumentInputType::value("(Reds) Magenta", TaggedValue::F64(0.), false),
|
DocumentInputType::value("(Reds) Magenta", TaggedValue::F64(0.), false),
|
||||||
@@ -1926,7 +1926,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentInputType::value("(Blacks) Yellow", TaggedValue::F64(0.), false),
|
DocumentInputType::value("(Blacks) Yellow", TaggedValue::F64(0.), false),
|
||||||
DocumentInputType::value("(Blacks) Black", TaggedValue::F64(0.), false),
|
DocumentInputType::value("(Blacks) Black", TaggedValue::F64(0.), false),
|
||||||
// Display-only properties (not used within the node)
|
// Display-only properties (not used within the node)
|
||||||
DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::Reds), false),
|
DocumentInputType::value("Colors", TaggedValue::SelectiveColorChoice(SelectiveColorChoice::default()), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||||
properties: node_properties::adjust_selective_color_properties,
|
properties: node_properties::adjust_selective_color_properties,
|
||||||
@@ -2347,11 +2347,41 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"),
|
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::PathGenerator<_>"),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
DocumentInputType::value("Path Data", TaggedValue::Subpaths(vec![]), false),
|
||||||
DocumentInputType::value("Colinear Manipulators", TaggedValue::ManipulatorGroupIds(vec![]), false),
|
DocumentInputType::value("Colinear Manipulators", TaggedValue::PointIds(vec![]), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
name: "Path",
|
||||||
|
category: "Vector",
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
|
nodes: vec![
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::network(concrete!(VectorData), 0)],
|
||||||
|
..monitor_node()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
name: "Path Modify".to_string(),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_core::vector::VectorModification), 1)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::PathModify<_>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![
|
||||||
|
DocumentInputType::value("Vector Data", TaggedValue::VectorData(VectorData::empty()), true),
|
||||||
|
DocumentInputType::value("Modification", TaggedValue::VectorModification(Default::default()), false),
|
||||||
|
],
|
||||||
|
outputs: vec![DocumentOutputType::new("Vector Data", FrontendGraphDataType::VectorData)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
name: "Sample",
|
name: "Sample",
|
||||||
category: "Structural",
|
category: "Structural",
|
||||||
@@ -2489,8 +2519,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentInputType::value("Weight", TaggedValue::F64(0.), false),
|
DocumentInputType::value("Weight", TaggedValue::F64(0.), false),
|
||||||
DocumentInputType::value("Dash Lengths", TaggedValue::VecF64(Vec::new()), false),
|
DocumentInputType::value("Dash Lengths", TaggedValue::VecF64(Vec::new()), false),
|
||||||
DocumentInputType::value("Dash Offset", TaggedValue::F64(0.), false),
|
DocumentInputType::value("Dash Offset", TaggedValue::F64(0.), false),
|
||||||
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt), false),
|
DocumentInputType::value("Line Cap", TaggedValue::LineCap(graphene_core::vector::style::LineCap::default()), false),
|
||||||
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter), false),
|
DocumentInputType::value("Line Join", TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::default()), false),
|
||||||
DocumentInputType::value("Miter Limit", TaggedValue::F64(4.), false),
|
DocumentInputType::value("Miter Limit", TaggedValue::F64(4.), false),
|
||||||
],
|
],
|
||||||
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
outputs: vec![DocumentOutputType::new("Vector", FrontendGraphDataType::VectorData)],
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ use bezier_rs::Subpath;
|
|||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, FlowType, NodeId, NodeInput, NodeNetwork, Previewing, Source};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, FlowType, NodeId, NodeInput, NodeNetwork, Previewing, Source};
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
|
||||||
use graphene_core::*;
|
use graphene_core::*;
|
||||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||||
|
use renderer::{ClickTarget, Quad};
|
||||||
|
use vector::PointId;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||||
use renderer::{ClickTarget, Quad};
|
|
||||||
use web_sys::window;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NodeGraphHandlerData<'a> {
|
pub struct NodeGraphHandlerData<'a> {
|
||||||
@@ -62,7 +61,7 @@ pub struct NodeGraphMessageHandler {
|
|||||||
/// Click targets for every node in the network by using the path to that node.
|
/// Click targets for every node in the network by using the path to that node.
|
||||||
pub node_metadata: HashMap<NodeId, NodeMetadata>,
|
pub node_metadata: HashMap<NodeId, NodeMetadata>,
|
||||||
/// Cache for the bounding box around all nodes in node graph space.
|
/// Cache for the bounding box around all nodes in node graph space.
|
||||||
pub bounding_box_subpath: Option<Subpath<ManipulatorGroupId>>,
|
pub bounding_box_subpath: Option<Subpath<PointId>>,
|
||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1548,8 +1547,15 @@ impl NodeGraphMessageHandler {
|
|||||||
common
|
common
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn get_text_width(node: &DocumentNode) -> Option<f64> {
|
fn get_text_width(node: &DocumentNode) -> Option<f64> {
|
||||||
let document = window().unwrap().document().unwrap();
|
warn!("Failed to find width of {node:#?} due to non-wasm arch");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
fn get_text_width(node: &DocumentNode) -> Option<f64> {
|
||||||
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
let div = match document.create_element("div") {
|
let div = match document.create_element("div") {
|
||||||
Ok(div) => div,
|
Ok(div) => div,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1594,6 +1600,7 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
Some(text_width)
|
Some(text_width)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer_width_cells(node: &DocumentNode) -> u32 {
|
pub fn layer_width_cells(node: &DocumentNode) -> u32 {
|
||||||
let half_grid_cell_offset = 24. / 2.;
|
let half_grid_cell_offset = 24. / 2.;
|
||||||
let thumbnail_width = 3. * 24.;
|
let thumbnail_width = 3. * 24.;
|
||||||
|
|||||||
@@ -1733,25 +1733,7 @@ pub fn logic_operator_properties(document_node: &DocumentNode, node_id: NodeId,
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let translation_assist = |widgets: &mut Vec<WidgetHolder>| {
|
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, add_blank_assist);
|
||||||
let pivot_index = 5;
|
|
||||||
if let NodeInput::Value {
|
|
||||||
tagged_value: TaggedValue::DVec2(pivot),
|
|
||||||
exposed: false,
|
|
||||||
} = document_node.inputs[pivot_index]
|
|
||||||
{
|
|
||||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
|
||||||
widgets.push(
|
|
||||||
PivotInput::new(pivot.into())
|
|
||||||
.on_update(update_value(|pivot: &PivotInput| TaggedValue::DVec2(Into::<Option<DVec2>>::into(pivot.position).unwrap()), node_id, 5))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
add_blank_assist(widgets);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, translation_assist);
|
|
||||||
|
|
||||||
let rotation = {
|
let rotation = {
|
||||||
let index = 2;
|
let index = 2;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ impl FrontendGraphDataType {
|
|||||||
pub fn with_type(input: &Type) -> Self {
|
pub fn with_type(input: &Type) -> Self {
|
||||||
match TaggedValue::from_type(input) {
|
match TaggedValue::from_type(input) {
|
||||||
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
|
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
|
||||||
TaggedValue::Subpaths(_) | TaggedValue::RcSubpath(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||||
TaggedValue::U32(_)
|
TaggedValue::U32(_)
|
||||||
| TaggedValue::U64(_)
|
| TaggedValue::U64(_)
|
||||||
| TaggedValue::F64(_)
|
| TaggedValue::F64(_)
|
||||||
|
|||||||
@@ -37,11 +37,10 @@ fn grid_overlay_rectangular(document: &DocumentMessageHandler, overlay_context:
|
|||||||
} else {
|
} else {
|
||||||
DVec2::new(secondary_pos, primary_end)
|
DVec2::new(secondary_pos, primary_end)
|
||||||
};
|
};
|
||||||
overlay_context.line(
|
overlay_context.colored_line(
|
||||||
document_to_viewport.transform_point2(start),
|
document_to_viewport.transform_point2(start),
|
||||||
document_to_viewport.transform_point2(end),
|
document_to_viewport.transform_point2(end),
|
||||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
&("#".to_string() + &grid_color.rgba_hex()),
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,11 +111,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
|||||||
let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;
|
let x_pos = (((min_x - origin.x) / spacing).ceil() + line_index as f64) * spacing + origin.x;
|
||||||
let start = DVec2::new(x_pos, min_y);
|
let start = DVec2::new(x_pos, min_y);
|
||||||
let end = DVec2::new(x_pos, max_y);
|
let end = DVec2::new(x_pos, max_y);
|
||||||
overlay_context.line(
|
overlay_context.colored_line(
|
||||||
document_to_viewport.transform_point2(start),
|
document_to_viewport.transform_point2(start),
|
||||||
document_to_viewport.transform_point2(end),
|
document_to_viewport.transform_point2(end),
|
||||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
&("#".to_string() + &grid_color.rgba_hex()),
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,11 +129,10 @@ fn grid_overlay_isometric(document: &DocumentMessageHandler, overlay_context: &m
|
|||||||
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;
|
let y_pos = (((inverse_project(&min_y) - origin.y) / spacing).ceil() + line_index as f64) * spacing + origin.y;
|
||||||
let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));
|
let start = DVec2::new(min_x, project(&DVec2::new(min_x, y_pos)));
|
||||||
let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));
|
let end = DVec2::new(max_x, project(&DVec2::new(max_x, y_pos)));
|
||||||
overlay_context.line(
|
overlay_context.colored_line(
|
||||||
document_to_viewport.transform_point2(start),
|
document_to_viewport.transform_point2(start),
|
||||||
document_to_viewport.transform_point2(end),
|
document_to_viewport.transform_point2(end),
|
||||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
&("#".to_string() + &grid_color.rgba_hex()),
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,7 +175,7 @@ fn grid_overlay_isometric_dot(document: &DocumentMessageHandler, overlay_context
|
|||||||
let start = DVec2::new(min_x + x_offset, project(&DVec2::new(min_x + x_offset, y_pos)));
|
let start = DVec2::new(min_x + x_offset, project(&DVec2::new(min_x + x_offset, y_pos)));
|
||||||
let end = DVec2::new(max_x + x_offset, project(&DVec2::new(max_x + x_offset, y_pos)));
|
let end = DVec2::new(max_x + x_offset, project(&DVec2::new(max_x + x_offset, y_pos)));
|
||||||
|
|
||||||
overlay_context.line(
|
overlay_context.dashed_line(
|
||||||
document_to_viewport.transform_point2(start),
|
document_to_viewport.transform_point2(start),
|
||||||
document_to_viewport.transform_point2(end),
|
document_to_viewport.transform_point2(end),
|
||||||
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
Some(&("#".to_string() + &grid_color.rgba_hex())),
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use super::utility_types::OverlayContext;
|
use super::utility_types::OverlayContext;
|
||||||
use crate::consts::HIDE_HANDLE_DISTANCE;
|
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_manipulator_groups, get_subpaths};
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
|
||||||
|
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::ManipulatorPointId;
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@@ -26,54 +25,55 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
|
|||||||
|
|
||||||
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { continue };
|
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||||
overlay_context.outline(subpaths.iter(), transform);
|
overlay_context.outline_vector(&vector_data, transform);
|
||||||
|
|
||||||
for manipulator_group in get_manipulator_groups(subpaths) {
|
for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() {
|
||||||
let anchor = manipulator_group.anchor;
|
let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||||
let anchor_position = transform.transform_point2(anchor);
|
let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||||
|
match bezier.handles {
|
||||||
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
|
||||||
if let Some(in_handle) = manipulator_group.in_handle.filter(not_under_anchor) {
|
overlay_context.line(handle, bezier.start);
|
||||||
let handle_position = transform.transform_point2(in_handle);
|
overlay_context.line(handle, bezier.end);
|
||||||
overlay_context.line(handle_position, anchor_position, None, None);
|
overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)));
|
||||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle)));
|
}
|
||||||
|
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||||
|
if not_under_anchor(handle_start, bezier.start) {
|
||||||
|
overlay_context.line(handle_start, bezier.start);
|
||||||
|
overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)));
|
||||||
|
}
|
||||||
|
if not_under_anchor(handle_end, bezier.end) {
|
||||||
|
overlay_context.line(handle_end, bezier.end);
|
||||||
|
overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
if let Some(out_handle) = manipulator_group.out_handle.filter(not_under_anchor) {
|
}
|
||||||
let handle_position = transform.transform_point2(out_handle);
|
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||||
overlay_context.line(handle_position, anchor_position, None, None);
|
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(selected, ManipulatorPointId::Anchor(id)), None);
|
||||||
overlay_context.manipulator_handle(handle_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle)));
|
|
||||||
}
|
|
||||||
|
|
||||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor)), None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
|
||||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { continue };
|
let Some(vector_data) = document.metadata.compute_modified_vector(layer, &document.network) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||||
|
|
||||||
let mut manipulator_groups = get_manipulator_groups(subpaths);
|
for point in vector_data.single_connected_points() {
|
||||||
|
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||||
if let Some(first_manipulator) = manipulator_groups.next() {
|
let position = transform.transform_point2(position);
|
||||||
let anchor = first_manipulator.anchor;
|
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
|
||||||
let anchor_position = transform.transform_point2(anchor);
|
}
|
||||||
|
|
||||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(first_manipulator.id, SelectedType::Anchor)), None);
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(last_manipulator) = manipulator_groups.last() {
|
|
||||||
let anchor = last_manipulator.anchor;
|
|
||||||
let anchor_position = transform.transform_point2(anchor);
|
|
||||||
|
|
||||||
overlay_context.manipulator_anchor(anchor_position, is_selected(selected, ManipulatorPointId::new(last_manipulator.id, SelectedType::Anchor)), None);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,14 @@ use super::utility_functions::overlay_canvas_context;
|
|||||||
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER};
|
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
|
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::{Bezier, Subpath};
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use wasm_bindgen::JsValue;
|
use graphene_std::vector::{PointId, VectorData};
|
||||||
|
|
||||||
|
use core::borrow::Borrow;
|
||||||
use core::f64::consts::TAU;
|
use core::f64::consts::TAU;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||||
|
|
||||||
@@ -39,7 +41,15 @@ impl OverlayContext {
|
|||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>) {
|
pub fn line(&mut self, start: DVec2, end: DVec2) {
|
||||||
|
self.dashed_line(start, end, None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn colored_line(&mut self, start: DVec2, end: DVec2, color: &str) {
|
||||||
|
self.dashed_line(start, end, Some(color), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, dash_width: Option<f64>) {
|
||||||
let start = start.round() - DVec2::splat(0.5);
|
let start = start.round() - DVec2::splat(0.5);
|
||||||
let end = end.round() - DVec2::splat(0.5);
|
let end = end.round() - DVec2::splat(0.5);
|
||||||
if let Some(dash_width) = dash_width {
|
if let Some(dash_width) = dash_width {
|
||||||
@@ -152,9 +162,44 @@ impl OverlayContext {
|
|||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline<'a, Id: bezier_rs::Identifier>(&mut self, subpaths: impl Iterator<Item = &'a Subpath<Id>>, transform: DAffine2) {
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
let mut last_point = None;
|
||||||
|
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||||
|
let move_to = last_point != Some(start_id);
|
||||||
|
last_point = Some(end_id);
|
||||||
|
|
||||||
|
self.bezier_command(bezier, transform, move_to);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.bezier_command(bezier, transform, true);
|
||||||
|
self.render_context.set_stroke_style(&wasm_bindgen::JsValue::from_str(COLOR_OVERLAY_BLUE));
|
||||||
|
self.render_context.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||||
|
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||||
|
if move_to {
|
||||||
|
self.render_context.move_to(start.x, start.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
match handles {
|
||||||
|
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
||||||
|
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
||||||
|
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline<'a>(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) {
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
for subpath in subpaths {
|
for subpath in subpaths {
|
||||||
|
let subpath = subpath.borrow();
|
||||||
let mut curves = subpath.iter().peekable();
|
let mut curves = subpath.iter().peekable();
|
||||||
|
|
||||||
let Some(first) = curves.peek() else {
|
let Some(first) = curves.peek() else {
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
use super::nodes::SelectedNodes;
|
use super::nodes::SelectedNodes;
|
||||||
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
|
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::FlowType;
|
use graph_craft::document::FlowType;
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
use graphene_core::renderer::ClickTarget;
|
use graphene_core::renderer::ClickTarget;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
|
use graphene_std::vector::PointId;
|
||||||
|
use graphene_std::vector::VectorData;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::vector::PointId;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ pub struct DocumentMetadata {
|
|||||||
hidden: HashSet<NodeId>,
|
hidden: HashSet<NodeId>,
|
||||||
locked: HashSet<NodeId>,
|
locked: HashSet<NodeId>,
|
||||||
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||||
|
vector_modify: HashMap<NodeId, VectorData>,
|
||||||
/// Transform from document space to viewport space.
|
/// Transform from document space to viewport space.
|
||||||
pub document_to_viewport: DAffine2,
|
pub document_to_viewport: DAffine2,
|
||||||
}
|
}
|
||||||
@@ -39,6 +43,7 @@ impl Default for DocumentMetadata {
|
|||||||
folders: HashSet::new(),
|
folders: HashSet::new(),
|
||||||
hidden: HashSet::new(),
|
hidden: HashSet::new(),
|
||||||
locked: HashSet::new(),
|
locked: HashSet::new(),
|
||||||
|
vector_modify: HashMap::new(),
|
||||||
click_targets: HashMap::new(),
|
click_targets: HashMap::new(),
|
||||||
document_to_viewport: DAffine2::IDENTITY,
|
document_to_viewport: DAffine2::IDENTITY,
|
||||||
}
|
}
|
||||||
@@ -62,6 +67,23 @@ impl DocumentMetadata {
|
|||||||
self.click_targets.get(&layer)
|
self.click_targets.get(&layer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get vector data after the modification is appled
|
||||||
|
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier, network: &NodeNetwork) -> Option<VectorData> {
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network);
|
||||||
|
|
||||||
|
if let Some(vector_data) = graph_layer.upstream_node_id_from_name("Path").and_then(|node| self.vector_modify.get(&node)) {
|
||||||
|
let mut modified = vector_data.clone();
|
||||||
|
if let Some(TaggedValue::VectorModification(modification)) = graph_layer.find_input("Path", 1) {
|
||||||
|
modification.apply(&mut modified);
|
||||||
|
}
|
||||||
|
return Some(modified);
|
||||||
|
}
|
||||||
|
self.click_targets
|
||||||
|
.get(&layer)
|
||||||
|
.map(|click| click.iter().map(|click| &click.subpath))
|
||||||
|
.map(|subpaths| VectorData::from_subpaths(subpaths, true))
|
||||||
|
}
|
||||||
|
|
||||||
/// Access the [`NodeRelations`] of a layer.
|
/// Access the [`NodeRelations`] of a layer.
|
||||||
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
|
fn get_relations(&self, node_identifier: LayerNodeIdentifier) -> Option<&NodeRelations> {
|
||||||
self.structure.get(&node_identifier)
|
self.structure.get(&node_identifier)
|
||||||
@@ -225,6 +247,7 @@ impl DocumentMetadata {
|
|||||||
|
|
||||||
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
|
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
|
||||||
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
|
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
|
||||||
|
self.vector_modify.retain(|node, _| graph.nodes.contains_key(node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,9 +304,10 @@ impl DocumentMetadata {
|
|||||||
// ===============================
|
// ===============================
|
||||||
|
|
||||||
impl DocumentMetadata {
|
impl DocumentMetadata {
|
||||||
/// Update the cached click targets of the layers
|
/// Update the cached click targets and vector modify values of the layers
|
||||||
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
|
pub fn update_from_monitor(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, new_vector_modify: HashMap<NodeId, VectorData>) {
|
||||||
self.click_targets = new_click_targets;
|
self.click_targets = new_click_targets;
|
||||||
|
self.vector_modify = new_vector_modify;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the bounding box of the click target of the specified layer in the specified transform space
|
/// Get the bounding box of the click target of the specified layer in the specified transform space
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::{TransformIn, VectorDataModification};
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
@@ -8,15 +8,37 @@ use crate::messages::tool::utility_types::ToolType;
|
|||||||
|
|
||||||
use graph_craft::document::NodeNetwork;
|
use graph_craft::document::NodeNetwork;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::ManipulatorPointId;
|
||||||
|
use graphene_core::vector::VectorModificationType;
|
||||||
|
use graphene_std::vector::{HandleId, PointId};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
struct AnchorPoint {
|
||||||
|
initial: DVec2,
|
||||||
|
current: DVec2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
struct HandlePoint {
|
||||||
|
initial: DVec2,
|
||||||
|
relative: DVec2,
|
||||||
|
anchor: PointId,
|
||||||
|
mirror: Option<(HandleId, DVec2)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct InitialPoints {
|
||||||
|
anchors: HashMap<PointId, AnchorPoint>,
|
||||||
|
handles: HashMap<HandleId, HandlePoint>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum OriginalTransforms {
|
pub enum OriginalTransforms {
|
||||||
Layer(HashMap<LayerNodeIdentifier, DAffine2>),
|
Layer(HashMap<LayerNodeIdentifier, DAffine2>),
|
||||||
Path(HashMap<LayerNodeIdentifier, Vec<(ManipulatorPointId, DVec2)>>),
|
Path(HashMap<LayerNodeIdentifier, InitialPoints>),
|
||||||
}
|
}
|
||||||
impl Default for OriginalTransforms {
|
impl Default for OriginalTransforms {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -43,36 +65,46 @@ impl OriginalTransforms {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
OriginalTransforms::Path(path_map) => {
|
OriginalTransforms::Path(path_map) => {
|
||||||
|
let Some(shape_editor) = shape_editor else {
|
||||||
|
warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType");
|
||||||
|
return;
|
||||||
|
};
|
||||||
for &layer in selected {
|
for &layer in selected {
|
||||||
let Some(shape_editor) = shape_editor else {
|
|
||||||
warn!("No shape editor structure found, which only happens in select tool, which cannot reach this point as we check for ToolType");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
// Anchors also move their handles
|
|
||||||
let expand_anchors = |&point: &ManipulatorPointId| {
|
|
||||||
if point.manipulator_type.is_handle() {
|
|
||||||
[Some(point), None, None]
|
|
||||||
} else {
|
|
||||||
[
|
|
||||||
Some(point),
|
|
||||||
Some(ManipulatorPointId::new(point.group, SelectedType::InHandle)),
|
|
||||||
Some(ManipulatorPointId::new(point.group, SelectedType::OutHandle)),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let points = shape_editor.selected_points().flat_map(expand_anchors).flatten();
|
|
||||||
if path_map.contains_key(&layer) {
|
if path_map.contains_key(&layer) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(vector_data) = graph_modification_utils::get_subpaths(layer, document_network) else {
|
let Some(vector_data) = document_metadata.compute_modified_vector(layer, document_network) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let get_manipulator_point_position = |point_id: ManipulatorPointId| {
|
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||||
graph_modification_utils::get_manipulator_from_id(vector_data, point_id.group)
|
continue;
|
||||||
.and_then(|manipulator_group| point_id.manipulator_type.get_position(manipulator_group))
|
|
||||||
.map(|position| (point_id, position))
|
|
||||||
};
|
};
|
||||||
path_map.insert(layer, points.filter_map(get_manipulator_point_position).collect());
|
|
||||||
|
// Anchors also move their handles
|
||||||
|
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||||
|
let anchors = anchor_ids.filter_map(|id| vector_data.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
|
||||||
|
let anchors = anchors.collect();
|
||||||
|
|
||||||
|
let selected_handles = selected_points.iter().filter_map(|point| point.as_handle());
|
||||||
|
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||||
|
let connected_handles = anchor_ids.flat_map(|point| vector_data.segment_domain.all_connected(point));
|
||||||
|
let all_handles = selected_handles.chain(connected_handles);
|
||||||
|
|
||||||
|
let handles = all_handles
|
||||||
|
.filter_map(|id| {
|
||||||
|
let anchor = id.to_manipulator_point().get_anchor(&vector_data)?;
|
||||||
|
let initial = id.to_manipulator_point().get_position(&vector_data)?;
|
||||||
|
let relative = vector_data.point_domain.position_from_id(anchor)?;
|
||||||
|
let other_handle = vector_data
|
||||||
|
.other_colinear_handle(id)
|
||||||
|
.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));
|
||||||
|
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector_data)?)));
|
||||||
|
|
||||||
|
Some((id, HandlePoint { initial, relative, anchor, mirror }))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
path_map.insert(layer, InitialPoints { anchors, handles });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,28 +414,31 @@ impl<'a> Selected<'a> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_path(
|
fn transform_path(document_metadata: &DocumentMetadata, layer: LayerNodeIdentifier, initial_points: &mut InitialPoints, transformation: DAffine2, responses: &mut VecDeque<Message>) {
|
||||||
document_metadata: &DocumentMetadata,
|
|
||||||
layer: LayerNodeIdentifier,
|
|
||||||
initial_points: Option<&Vec<(ManipulatorPointId, DVec2)>>,
|
|
||||||
transformation: DAffine2,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
) {
|
|
||||||
let viewspace = document_metadata.transform_to_viewport(layer);
|
let viewspace = document_metadata.transform_to_viewport(layer);
|
||||||
let layerspace_rotation = viewspace.inverse() * transformation;
|
let layerspace_rotation = viewspace.inverse() * transformation;
|
||||||
|
|
||||||
let Some(initial_points) = initial_points else {
|
for (&point, anchor) in initial_points.anchors.iter_mut() {
|
||||||
return;
|
let new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(anchor.initial));
|
||||||
};
|
let delta = new_pos_viewport - anchor.current;
|
||||||
|
anchor.current += delta;
|
||||||
|
let modification_type = VectorModificationType::ApplyPointDelta { point, delta };
|
||||||
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
}
|
||||||
|
|
||||||
for (point_id, position) in initial_points {
|
for (&id, handle) in initial_points.handles.iter() {
|
||||||
let viewport_point = viewspace.transform_point2(*position);
|
let new_pos_viewport = layerspace_rotation.transform_point2(viewspace.transform_point2(handle.initial));
|
||||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
|
let relative = initial_points.anchors.get(&handle.anchor).map_or(handle.relative, |anchor| anchor.current);
|
||||||
let point = *point_id;
|
let modification_type = id.set_relative_position(new_pos_viewport - relative);
|
||||||
let position = new_pos_viewport;
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
let modification = VectorDataModification::SetManipulatorPosition { point, position };
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification });
|
if let Some((id, initial)) = handle.mirror {
|
||||||
|
let direction = viewspace.transform_vector2(new_pos_viewport - relative).try_normalize();
|
||||||
|
let length = viewspace.transform_vector2(initial - relative).length();
|
||||||
|
let new_relative = direction.map_or(initial - relative, |direction| viewspace.inverse().transform_vector2(-direction * length));
|
||||||
|
let modification_type = id.set_relative_position(new_relative);
|
||||||
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,9 +448,13 @@ impl<'a> Selected<'a> {
|
|||||||
for layer_ancestors in self.document_metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
for layer_ancestors in self.document_metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
||||||
let layer = *layer_ancestors.last().unwrap();
|
let layer = *layer_ancestors.last().unwrap();
|
||||||
|
|
||||||
match &self.original_transforms {
|
match &mut self.original_transforms {
|
||||||
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document_metadata, layer, layer_transforms.get(&layer), transformation, self.responses),
|
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document_metadata, layer, layer_transforms.get(&layer), transformation, self.responses),
|
||||||
OriginalTransforms::Path(path_transforms) => Self::transform_path(self.document_metadata, layer, path_transforms.get(&layer), transformation, self.responses),
|
OriginalTransforms::Path(path_transforms) => {
|
||||||
|
if let Some(initial_points) = path_transforms.get_mut(&layer) {
|
||||||
|
Self::transform_path(self.document_metadata, layer, initial_points, transformation, self.responses)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,11 +481,20 @@ impl<'a> Selected<'a> {
|
|||||||
}
|
}
|
||||||
OriginalTransforms::Path(path) => {
|
OriginalTransforms::Path(path) => {
|
||||||
for (&layer, points) in path {
|
for (&layer, points) in path {
|
||||||
for &(point, position) in points {
|
for (&point, &anchor) in &points.anchors {
|
||||||
self.responses.add(GraphOperationMessage::Vector {
|
let delta = anchor.initial - anchor.current;
|
||||||
layer,
|
let modification_type = VectorModificationType::ApplyPointDelta { point, delta };
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
});
|
}
|
||||||
|
|
||||||
|
for (&point, &handle) in &points.handles {
|
||||||
|
let modification_type = point.set_relative_position(handle.initial - handle.relative);
|
||||||
|
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
|
if let Some((id, initial)) = handle.mirror {
|
||||||
|
let modification_type = id.set_relative_position(initial - handle.relative);
|
||||||
|
self.responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ pub struct PortfolioMessageData<'a> {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct PortfolioMessageHandler {
|
pub struct PortfolioMessageHandler {
|
||||||
menu_bar_message_handler: MenuBarMessageHandler,
|
menu_bar_message_handler: MenuBarMessageHandler,
|
||||||
documents: HashMap<DocumentId, DocumentMessageHandler>,
|
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||||
document_ids: Vec<DocumentId>,
|
document_ids: Vec<DocumentId>,
|
||||||
active_document_id: Option<DocumentId>,
|
pub(crate) active_document_id: Option<DocumentId>,
|
||||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||||
pub persistent_data: PersistentData,
|
pub persistent_data: PersistentData,
|
||||||
pub executor: NodeGraphExecutor,
|
pub executor: NodeGraphExecutor,
|
||||||
@@ -375,6 +375,20 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||||||
document_is_saved,
|
document_is_saved,
|
||||||
document_serialized_content,
|
document_serialized_content,
|
||||||
} => {
|
} => {
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
let do_not_upgrade = document_name.contains("__DO_NOT_UPGRADE__");
|
||||||
|
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
|
||||||
|
if document_serialized_content.contains("ManipulatorGroupIds") && !do_not_upgrade {
|
||||||
|
responses.add(FrontendMessage::TriggerUpgradeDocumentToVectorManipulationFormat {
|
||||||
|
document_id,
|
||||||
|
document_name,
|
||||||
|
document_is_auto_saved,
|
||||||
|
document_is_saved,
|
||||||
|
document_serialized_content,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let document = DocumentMessageHandler::with_name_and_content(document_name, document_serialized_content);
|
let document = DocumentMessageHandler::with_name_and_content(document_name, document_serialized_content);
|
||||||
match document {
|
match document {
|
||||||
Ok(mut document) => {
|
Ok(mut document) => {
|
||||||
@@ -656,12 +670,13 @@ impl PortfolioMessageHandler {
|
|||||||
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
|
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
let Some(active_document) = self.active_document_id.and_then(|id| self.documents.get_mut(&id)) else {
|
||||||
return;
|
return Err("No active document".to_string());
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.executor.poll_node_graph_evaluation(active_document, responses).is_err() {
|
let result = self.executor.poll_node_graph_evaluation(active_document, responses);
|
||||||
|
if result.is_err() {
|
||||||
let error = r#"
|
let error = r#"
|
||||||
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
||||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
||||||
@@ -673,5 +688,6 @@ impl PortfolioMessageHandler {
|
|||||||
.to_string();
|
.to_string();
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
|
||||||
}
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
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_core::Color;
|
||||||
use graphene_std::vector::style::FillChoice;
|
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(
|
pub fn create_widgets(
|
||||||
&self,
|
&self,
|
||||||
label_text: impl Into<String>,
|
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::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use bezier_rs::Subpath;
|
||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
|
||||||
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
use graph_craft::document::{value::TaggedValue, DocumentNode, NodeId, NodeInput, NodeNetwork};
|
||||||
use graphene_core::raster::{BlendMode, ImageFrame};
|
use graphene_core::raster::{BlendMode, ImageFrame};
|
||||||
use graphene_core::text::Font;
|
use graphene_core::text::Font;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
|
||||||
use graphene_core::vector::style::Gradient;
|
use graphene_core::vector::style::Gradient;
|
||||||
|
use graphene_core::vector::PointId;
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
/// 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;
|
let insert_index = -1;
|
||||||
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });
|
responses.add(GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index });
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
|
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)
|
LayerNodeIdentifier::new_unchecked(id)
|
||||||
}
|
}
|
||||||
|
pub fn new_custom(id: NodeId, nodes: HashMap<NodeId, DocumentNode>, parent: LayerNodeIdentifier, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
|
||||||
/// Batch set all of the manipulator groups to set their colinear handle state on a specific layer
|
responses.add(GraphOperationMessage::NewCustomLayer {
|
||||||
pub fn set_manipulator_colinear_handles_state(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>], layer: LayerNodeIdentifier, colinear: bool, responses: &mut VecDeque<Message>) {
|
id,
|
||||||
for manipulator_group in manipulator_groups {
|
nodes,
|
||||||
responses.add(GraphOperationMessage::Vector {
|
parent,
|
||||||
layer,
|
insert_index: -1,
|
||||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id: manipulator_group.id, colinear },
|
alias: String::new(),
|
||||||
});
|
});
|
||||||
}
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![id] });
|
||||||
}
|
LayerNodeIdentifier::new_unchecked(id)
|
||||||
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work)
|
/// 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)
|
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
|
/// Get the current gradient of a layer from the closest Fill node
|
||||||
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
|
pub fn get_gradient(layer: LayerNodeIdentifier, document_network: &NodeNetwork) -> Option<Gradient> {
|
||||||
let fill_index = 1;
|
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()
|
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.
|
/// An immutable reference to a layer within the document node graph for easy access.
|
||||||
pub struct NodeGraphLayer<'a> {
|
pub struct NodeGraphLayer<'a> {
|
||||||
node_graph: &'a NodeNetwork,
|
node_graph: &'a NodeNetwork,
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ impl Resize {
|
|||||||
root_transform.transform_point2(self.drag_start)
|
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> {
|
pub fn calculate_points(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, center: Key, lock_ratio: Key) -> Option<[DVec2; 2]> {
|
||||||
let Some(layer) = self.layer else {
|
let layer = self.layer?;
|
||||||
return None;
|
|
||||||
};
|
|
||||||
|
|
||||||
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
if layer == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
log::error!("Resize layer cannot be ROOT_PARENT");
|
log::error!("Resize layer cannot be ROOT_PARENT");
|
||||||
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !document.network().nodes.contains_key(&layer.to_node()) {
|
if !document.network().nodes.contains_key(&layer.to_node()) {
|
||||||
@@ -87,9 +86,14 @@ impl Resize {
|
|||||||
self.snap_manager.update_indicator(snapped);
|
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(
|
Some(
|
||||||
GraphOperationMessage::TransformSet {
|
GraphOperationMessage::TransformSet {
|
||||||
layer,
|
layer: self.layer?,
|
||||||
transform: DAffine2::from_scale_angle_translation(points_viewport[1] - points_viewport[0], 0., points_viewport[0]),
|
transform: DAffine2::from_scale_angle_translation(points_viewport[1] - points_viewport[0], 0., points_viewport[0]),
|
||||||
transform_in: TransformIn::Viewport,
|
transform_in: TransformIn::Viewport,
|
||||||
skip_rerender,
|
skip_rerender,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,20 @@
|
|||||||
mod grid_snapper;
|
mod grid_snapper;
|
||||||
mod layer_snapper;
|
mod layer_snapper;
|
||||||
mod snap_results;
|
mod snap_results;
|
||||||
|
pub use {grid_snapper::*, layer_snapper::*, snap_results::*};
|
||||||
|
|
||||||
use crate::consts::COLOR_OVERLAY_BLUE;
|
use crate::consts::COLOR_OVERLAY_BLUE;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, GridSnapTarget, SnapTarget};
|
use crate::messages::portfolio::document::utility_types::misc::{BoundingBoxSnapTarget, GeometrySnapTarget, GridSnapTarget, SnapTarget};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
use bezier_rs::{Subpath, TValue};
|
use bezier_rs::{Subpath, TValue};
|
||||||
use glam::{DAffine2, DVec2};
|
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::vector::PointId;
|
||||||
|
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
pub use {grid_snapper::*, layer_snapper::*, snap_results::*};
|
|
||||||
|
|
||||||
/// Handles snapping and snap overlays
|
/// Handles snapping and snap overlays
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
@@ -150,7 +153,7 @@ pub struct SnapData<'a> {
|
|||||||
pub document: &'a DocumentMessageHandler,
|
pub document: &'a DocumentMessageHandler,
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub ignore: &'a [LayerNodeIdentifier],
|
pub ignore: &'a [LayerNodeIdentifier],
|
||||||
pub manipulators: Vec<(LayerNodeIdentifier, ManipulatorGroupId)>,
|
pub manipulators: Vec<(LayerNodeIdentifier, PointId)>,
|
||||||
pub candidates: Option<&'a Vec<LayerNodeIdentifier>>,
|
pub candidates: Option<&'a Vec<LayerNodeIdentifier>>,
|
||||||
}
|
}
|
||||||
impl<'a> SnapData<'a> {
|
impl<'a> SnapData<'a> {
|
||||||
@@ -172,7 +175,7 @@ impl<'a> SnapData<'a> {
|
|||||||
fn ignore_bounds(&self, layer: LayerNodeIdentifier) -> bool {
|
fn ignore_bounds(&self, layer: LayerNodeIdentifier) -> bool {
|
||||||
self.manipulators.iter().any(|&(ignore, _)| ignore == layer)
|
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()))
|
self.manipulators.contains(&(layer, manipulator.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,10 +280,8 @@ impl SnapManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(root) = snap_data.document.network.get_root_node() {
|
for layer in LayerNodeIdentifier::ROOT_PARENT.children(&document.metadata) {
|
||||||
if snap_data.document.network.nodes.get(&root.id).expect("Root should always be a node in find_candidates").is_layer {
|
add_candidates(layer, snap_data, quad, &mut candidates);
|
||||||
add_candidates(LayerNodeIdentifier::new(root.id, &snap_data.document.network), snap_data, quad, &mut candidates);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if candidates.len() > 10 {
|
if candidates.len() > 10 {
|
||||||
@@ -333,7 +334,7 @@ impl SnapManager {
|
|||||||
if let Some(ind) = &self.indicator {
|
if let Some(ind) = &self.indicator {
|
||||||
for curve in &ind.curves {
|
for curve in &ind.curves {
|
||||||
let Some(curve) = curve else { continue };
|
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 {
|
if let Some(quad) = ind.target_bounds {
|
||||||
overlay_context.quad(to_viewport * quad);
|
overlay_context.quad(to_viewport * quad);
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ use crate::messages::prelude::*;
|
|||||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::vector::PointId;
|
||||||
use graphene_std::vector::PointId;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct LayerSnapper {
|
pub struct LayerSnapper {
|
||||||
@@ -33,7 +32,7 @@ impl LayerSnapper {
|
|||||||
self.paths_to_snap.push(SnapCandidatePath {
|
self.paths_to_snap.push(SnapCandidatePath {
|
||||||
document_curve,
|
document_curve,
|
||||||
layer,
|
layer,
|
||||||
start: ManipulatorGroupId::new(),
|
start: PointId::new(),
|
||||||
target,
|
target,
|
||||||
bounds: Some(bounds),
|
bounds: Some(bounds),
|
||||||
});
|
});
|
||||||
@@ -133,7 +132,7 @@ impl LayerSnapper {
|
|||||||
let direction = constraint.direction().normalize_or_zero();
|
let direction = constraint.direction().normalize_or_zero();
|
||||||
let start = constrained_point - tolerance * direction;
|
let start = constrained_point - tolerance * direction;
|
||||||
let end = 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 {
|
for path in &self.paths_to_snap {
|
||||||
@@ -296,7 +295,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||||||
struct SnapCandidatePath {
|
struct SnapCandidatePath {
|
||||||
document_curve: Bezier,
|
document_curve: Bezier,
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
start: ManipulatorGroupId,
|
start: PointId,
|
||||||
target: SnapTarget,
|
target: SnapTarget,
|
||||||
bounds: Option<Quad>,
|
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(group: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||||
pub fn are_manipulator_handles_colinear<Id: bezier_rs::Identifier>(group: &bezier_rs::ManipulatorGroup<Id>, to_document: DAffine2, subpath: &Subpath<Id>, index: usize) -> bool {
|
|
||||||
let anchor = group.anchor;
|
let anchor = group.anchor;
|
||||||
let handle_in = group.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
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));
|
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 bezier_rs::Bezier;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
use graphene_core::vector::PointId;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct SnapResults {
|
pub struct SnapResults {
|
||||||
@@ -79,7 +79,7 @@ pub struct SnappedLine {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SnappedCurve {
|
pub struct SnappedCurve {
|
||||||
pub layer: LayerNodeIdentifier,
|
pub layer: LayerNodeIdentifier,
|
||||||
pub start: ManipulatorGroupId,
|
pub start: PointId,
|
||||||
pub point: SnappedPoint,
|
pub point: SnappedPoint,
|
||||||
pub document_curve: Bezier,
|
pub document_curve: Bezier,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ impl SelectedEdges {
|
|||||||
for point in points {
|
for point in points {
|
||||||
let old_position = point.document_point;
|
let old_position = point.document_point;
|
||||||
let bounds_space = bounds_to_doc.inverse().transform_point2(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 normalized = (bounds_space - self.bounds[0]) / (self.bounds[1] - self.bounds[0]);
|
||||||
let updated = normalised * (max - min) + min;
|
let updated = normalized * (max - min) + min;
|
||||||
point.document_point = bounds_to_doc.transform_point2(updated);
|
point.document_point = bounds_to_doc.transform_point2(updated);
|
||||||
let mut snapped = if constrain {
|
let mut snapped = if constrain {
|
||||||
let constraint = SnapConstraint::Line {
|
let constraint = SnapConstraint::Line {
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
|
||||||
|
use graphene_std::vector::PointId;
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
/// Determines if a path should be extended. Returns the path and if it is extending from the start, if applicable.
|
/// 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 = None;
|
||||||
let mut best_distance_squared = tolerance * tolerance;
|
let mut best_distance_squared = tolerance * tolerance;
|
||||||
|
|
||||||
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
for layer in document.selected_nodes.selected_layers(document.metadata()) {
|
||||||
let viewspace = document.metadata().transform_to_viewport(layer);
|
let viewspace = document.metadata().transform_to_viewport(layer);
|
||||||
|
|
||||||
let subpaths = get_subpaths(layer, &document.network)?;
|
let vector_data = document.metadata.compute_modified_vector(layer, document.network())?;
|
||||||
for (subpath_index, subpath) in subpaths.iter().enumerate() {
|
for id in vector_data.single_connected_points() {
|
||||||
if subpath.closed() {
|
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (manipulator_group, from_start) in [(subpath.manipulator_groups().first(), true), (subpath.manipulator_groups().last(), false)] {
|
let distance_squared = viewspace.transform_point2(point).distance_squared(goal);
|
||||||
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, id, point));
|
||||||
if distance_squared < best_distance_squared {
|
best_distance_squared = distance_squared;
|
||||||
best = Some((layer, subpath_index, from_start));
|
|
||||||
best_distance_squared = distance_squared;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
@@ -7,9 +8,8 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -201,10 +201,18 @@ impl Fsm for EllipseToolFsmState {
|
|||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
// Create a new ellipse vector shape
|
// Create a new ellipse vector shape
|
||||||
let subpath = bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE);
|
let nodes = {
|
||||||
let manipulator_groups = subpath.manipulator_groups().to_vec();
|
let node_type = resolve_document_node_type("Ellipse").expect("Ellipse node does not exist");
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
|
let node = node_type.to_document_node_default_inputs(
|
||||||
graph_modification_utils::set_manipulator_colinear_handles_state(&manipulator_groups, layer, true, responses);
|
[None, Some(NodeInput::value(TaggedValue::F64(0.5), false)), Some(NodeInput::value(TaggedValue::F64(0.5), false))],
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
HashMap::from([(NodeId(0), node)])
|
||||||
|
};
|
||||||
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
|
||||||
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
shape_data.layer = Some(layer);
|
shape_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
@@ -214,22 +222,18 @@ impl Fsm for EllipseToolFsmState {
|
|||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let fill_color = tool_options.fill.active_color();
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
|
||||||
});
|
|
||||||
|
|
||||||
EllipseToolFsmState::Drawing
|
EllipseToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => {
|
(EllipseToolFsmState::Drawing, EllipseToolMessage::PointerMove { center, lock_ratio }) => {
|
||||||
if let Some(message) = shape_data.calculate_transform(document, input, center, lock_ratio, false) {
|
if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) {
|
||||||
responses.add(message);
|
if let Some(layer) = shape_data.layer {
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(end - start, 0., (start + end) / 2.),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
|
use crate::messages::portfolio::document::overlays::utility_functions::path_endpoint_overlays;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
@@ -9,10 +9,10 @@ use crate::messages::tool::common_functionality::utility_functions::should_exten
|
|||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
use graphene_core::vector::VectorModificationType;
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
use graphene_std::vector::{PointId, SegmentId};
|
||||||
|
|
||||||
use bezier_rs::ManipulatorGroup;
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -176,7 +176,7 @@ impl ToolTransition for FreehandTool {
|
|||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct FreehandToolData {
|
struct FreehandToolData {
|
||||||
extend_from_start: bool,
|
extend_from_start: bool,
|
||||||
last_point: DVec2,
|
end_point: Option<(DVec2, PointId)>,
|
||||||
dragged: bool,
|
dragged: bool,
|
||||||
weight: f64,
|
weight: f64,
|
||||||
layer: Option<LayerNodeIdentifier>,
|
layer: Option<LayerNodeIdentifier>,
|
||||||
@@ -211,64 +211,44 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
tool_data.extend_from_start = false;
|
tool_data.extend_from_start = false;
|
||||||
tool_data.weight = tool_options.line_weight;
|
tool_data.weight = tool_options.line_weight;
|
||||||
|
|
||||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
// Extend an endpoint of the selected path
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
if let Some((layer, _, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
|
||||||
let manipulator_group = ManipulatorGroup::new_anchor(pos);
|
|
||||||
let modification = if from_start {
|
|
||||||
tool_data.extend_from_start = true;
|
|
||||||
VectorDataModification::AddStartManipulatorGroup { subpath_index, manipulator_group }
|
|
||||||
} else {
|
|
||||||
VectorDataModification::AddEndManipulatorGroup { subpath_index, manipulator_group }
|
|
||||||
};
|
|
||||||
|
|
||||||
tool_data.dragged = true;
|
|
||||||
tool_data.last_point = pos;
|
|
||||||
tool_data.layer = Some(layer);
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification });
|
extend_path_with_next_segment(tool_data, position, responses);
|
||||||
} else {
|
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
|
||||||
|
|
||||||
let parent = document.new_layer_parent(true);
|
return FreehandToolFsmState::Drawing;
|
||||||
let transform = document.metadata().transform_to_viewport(parent);
|
|
||||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
|
||||||
let subpath = bezier_rs::Subpath::from_anchors([pos], false);
|
|
||||||
|
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses);
|
|
||||||
|
|
||||||
tool_data.last_point = pos;
|
|
||||||
tool_data.layer = Some(layer);
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::DeselectAllLayers);
|
||||||
|
|
||||||
|
let parent = document.new_layer_parent(true);
|
||||||
|
|
||||||
|
let nodes = {
|
||||||
|
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
||||||
|
let node = node_type.to_document_node_default_inputs([], Default::default());
|
||||||
|
|
||||||
|
HashMap::from([(NodeId(0), node)])
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);
|
||||||
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
|
let position = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
|
extend_path_with_next_segment(tool_data, position, responses);
|
||||||
|
|
||||||
FreehandToolFsmState::Drawing
|
FreehandToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => {
|
(FreehandToolFsmState::Drawing, FreehandToolMessage::PointerMove) => {
|
||||||
if let Some(layer) = tool_data.layer {
|
if let Some(layer) = tool_data.layer {
|
||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
let pos = transform.inverse().transform_point2(input.mouse.position);
|
let position = transform.inverse().transform_point2(input.mouse.position);
|
||||||
|
|
||||||
if tool_data.last_point != pos {
|
extend_path_with_next_segment(tool_data, position, responses);
|
||||||
let manipulator_group = ManipulatorGroup::new_anchor(pos);
|
|
||||||
let modification = if tool_data.extend_from_start {
|
|
||||||
VectorDataModification::AddStartManipulatorGroup { subpath_index: 0, manipulator_group }
|
|
||||||
} else {
|
|
||||||
VectorDataModification::AddEndManipulatorGroup { subpath_index: 0, manipulator_group }
|
|
||||||
};
|
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification });
|
|
||||||
tool_data.dragged = true;
|
|
||||||
tool_data.last_point = pos;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FreehandToolFsmState::Drawing
|
FreehandToolFsmState::Drawing
|
||||||
@@ -276,6 +256,8 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
(FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop) => {
|
(FreehandToolFsmState::Drawing, FreehandToolMessage::DragStop) => {
|
||||||
if tool_data.dragged {
|
if tool_data.dragged {
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
|
} else {
|
||||||
|
responses.add(DocumentMessage::DocumentHistoryBackward);
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.layer = None;
|
tool_data.layer = None;
|
||||||
@@ -312,3 +294,34 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVec2, responses: &mut VecDeque<Message>) {
|
||||||
|
if !tool_data.end_point.map_or(true, |(last_pos, _)| position != last_pos) || !position.is_finite() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(layer) = tool_data.layer else { return };
|
||||||
|
|
||||||
|
let id = PointId::generate();
|
||||||
|
responses.add(GraphOperationMessage::Vector {
|
||||||
|
layer,
|
||||||
|
modification_type: VectorModificationType::InsertPoint { id, position },
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some((_, previous_position)) = tool_data.end_point {
|
||||||
|
let next_id = SegmentId::generate();
|
||||||
|
let points = [previous_position, id];
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::Vector {
|
||||||
|
layer,
|
||||||
|
modification_type: VectorModificationType::InsertSegment {
|
||||||
|
id: next_id,
|
||||||
|
points,
|
||||||
|
handles: [None, None],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
tool_data.dragged = true;
|
||||||
|
tool_data.end_point = Some((position, id));
|
||||||
|
}
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
let Gradient { start, end, stops, .. } = gradient;
|
let Gradient { start, end, stops, .. } = gradient;
|
||||||
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
|
||||||
|
|
||||||
overlay_context.line(start, end, None, None);
|
overlay_context.line(start, end);
|
||||||
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start));
|
overlay_context.manipulator_handle(start, dragging == Some(GradientDragTarget::Start));
|
||||||
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End));
|
overlay_context.manipulator_handle(end, dragging == Some(GradientDragTarget::End));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
@@ -8,9 +9,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
|
|||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::Stroke;
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -177,11 +177,24 @@ impl Fsm for LineToolFsmState {
|
|||||||
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
||||||
tool_data.drag_start = snapped.snapped_point_document;
|
tool_data.drag_start = snapped.snapped_point_document;
|
||||||
|
|
||||||
let subpath = bezier_rs::Subpath::new_line(DVec2::ZERO, DVec2::X);
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
|
let nodes = {
|
||||||
|
let node_type = resolve_document_node_type("Line").expect("Line node does not exist");
|
||||||
|
let node = node_type.to_document_node_default_inputs(
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
Some(NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::DVec2(DVec2::X), false)),
|
||||||
|
],
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
HashMap::from([(NodeId(0), node)])
|
||||||
|
};
|
||||||
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
layer,
|
layer,
|
||||||
@@ -190,11 +203,6 @@ impl Fsm for LineToolFsmState {
|
|||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
|
||||||
});
|
|
||||||
|
|
||||||
tool_data.layer = Some(layer);
|
tool_data.layer = Some(layer);
|
||||||
tool_data.weight = tool_options.line_weight;
|
tool_data.weight = tool_options.line_weight;
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ use crate::messages::portfolio::document::overlays::utility_functions::path_over
|
|||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{get_colinear_manipulators, get_manipulator_from_id, get_subpaths};
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{ClosestSegment, ManipulatorAngle, ManipulatorPointInfo, OpposingHandleLengths, SelectedPointsInfo, ShapeState};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapData, SnapManager};
|
||||||
|
|
||||||
use graph_craft::document::NodeNetwork;
|
use graph_craft::document::NodeNetwork;
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
use graphene_core::vector::ManipulatorPointId;
|
||||||
|
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
@@ -92,10 +91,7 @@ impl LayoutHolder for PathTool {
|
|||||||
let (x, y) = coordinates.map(|point| (Some(point.x), Some(point.y))).unwrap_or((None, None));
|
let (x, y) = coordinates.map(|point| (Some(point.x), Some(point.y))).unwrap_or((None, None));
|
||||||
|
|
||||||
let selection_status = &self.tool_data.selection_status;
|
let selection_status = &self.tool_data.selection_status;
|
||||||
let manipulator_angle = selection_status
|
let manipulator_angle = selection_status.angle();
|
||||||
.as_multiple()
|
|
||||||
.map(|multiple| multiple.manipulator_angle)
|
|
||||||
.or_else(|| selection_status.as_one().map(|point| point.manipulator_angle));
|
|
||||||
|
|
||||||
let x_location = NumberInput::new(x)
|
let x_location = NumberInput::new(x)
|
||||||
.unit(" px")
|
.unit(" px")
|
||||||
@@ -105,8 +101,11 @@ impl LayoutHolder for PathTool {
|
|||||||
.min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))
|
.min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))
|
||||||
.max((1_u64 << std::f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << std::f64::MANTISSA_DIGITS) as f64)
|
||||||
.on_update(move |number_input: &NumberInput| {
|
.on_update(move |number_input: &NumberInput| {
|
||||||
let new_x = number_input.value.unwrap_or(x.unwrap());
|
if let Some(new_x) = number_input.value.or(x) {
|
||||||
PathToolMessage::SelectedPointXChanged { new_x }.into()
|
PathToolMessage::SelectedPointXChanged { new_x }.into()
|
||||||
|
} else {
|
||||||
|
Message::NoOp
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.widget_holder();
|
.widget_holder();
|
||||||
|
|
||||||
@@ -118,8 +117,11 @@ impl LayoutHolder for PathTool {
|
|||||||
.min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))
|
.min(-((1_u64 << std::f64::MANTISSA_DIGITS) as f64))
|
||||||
.max((1_u64 << std::f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << std::f64::MANTISSA_DIGITS) as f64)
|
||||||
.on_update(move |number_input: &NumberInput| {
|
.on_update(move |number_input: &NumberInput| {
|
||||||
let new_y = number_input.value.unwrap_or(y.unwrap());
|
if let Some(new_y) = number_input.value.or(y) {
|
||||||
PathToolMessage::SelectedPointYChanged { new_y }.into()
|
PathToolMessage::SelectedPointYChanged { new_y }.into()
|
||||||
|
} else {
|
||||||
|
Message::NoOp
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.widget_holder();
|
.widget_holder();
|
||||||
|
|
||||||
@@ -273,7 +275,7 @@ impl PathToolData {
|
|||||||
fn update_insertion(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, mouse_position: DVec2) -> PathToolFsmState {
|
fn update_insertion(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, mouse_position: DVec2) -> PathToolFsmState {
|
||||||
if let Some(closed_segment) = &mut self.segment {
|
if let Some(closed_segment) = &mut self.segment {
|
||||||
closed_segment.update_closest_point(&document.metadata, mouse_position);
|
closed_segment.update_closest_point(&document.metadata, mouse_position);
|
||||||
if closed_segment.too_far(mouse_position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE) {
|
if closed_segment.too_far(mouse_position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, &document.metadata) {
|
||||||
self.end_insertion(shape_editor, responses, InsertEndKind::Abort)
|
self.end_insertion(shape_editor, responses, InsertEndKind::Abort)
|
||||||
} else {
|
} else {
|
||||||
PathToolFsmState::InsertPoint
|
PathToolFsmState::InsertPoint
|
||||||
@@ -318,6 +320,8 @@ impl PathToolData {
|
|||||||
let document_network = document.network();
|
let document_network = document.network();
|
||||||
let document_metadata = document.metadata();
|
let document_metadata = document.metadata();
|
||||||
|
|
||||||
|
self.drag_start_pos = input.mouse.position;
|
||||||
|
|
||||||
// Select the first point within the threshold (in pixels)
|
// Select the first point within the threshold (in pixels)
|
||||||
if let Some(selected_points) = shape_editor.change_point_selection(document_network, document_metadata, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
|
if let Some(selected_points) = shape_editor.change_point_selection(document_network, document_metadata, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
|
||||||
if let Some(selected_points) = selected_points {
|
if let Some(selected_points) = selected_points {
|
||||||
@@ -345,7 +349,7 @@ impl PathToolData {
|
|||||||
}
|
}
|
||||||
self.drag_start_pos = input.mouse.position;
|
self.drag_start_pos = input.mouse.position;
|
||||||
self.previous_mouse_position = document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
|
self.previous_mouse_position = document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||||
shape_editor.select_all_anchors_in_layer(&document.network, layer);
|
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
||||||
|
|
||||||
PathToolFsmState::Dragging
|
PathToolFsmState::Dragging
|
||||||
}
|
}
|
||||||
@@ -370,16 +374,12 @@ impl PathToolData {
|
|||||||
// Do not snap against handles when anchor is selected
|
// Do not snap against handles when anchor is selected
|
||||||
let mut additional_selected_points = Vec::new();
|
let mut additional_selected_points = Vec::new();
|
||||||
for point in selected_points.points.iter() {
|
for point in selected_points.points.iter() {
|
||||||
if point.point_id.manipulator_type == SelectedType::Anchor {
|
let Some(anchor) = point.point_id.as_anchor() else { continue };
|
||||||
additional_selected_points.push(ManipulatorPointInfo {
|
|
||||||
layer: point.layer,
|
let connected = selected_points.vector_data.segment_domain.all_connected(anchor).map(|handle| handle.to_manipulator_point());
|
||||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::InHandle),
|
let filtered = connected.filter(|point| point.get_position(&selected_points.vector_data).is_some());
|
||||||
});
|
let point_info = filtered.map(|point_id| ManipulatorPointInfo { layer: point.layer, point_id });
|
||||||
additional_selected_points.push(ManipulatorPointInfo {
|
additional_selected_points.extend(point_info);
|
||||||
layer: point.layer,
|
|
||||||
point_id: ManipulatorPointId::new(point.point_id.group, SelectedType::OutHandle),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
selected_points.points.extend(additional_selected_points);
|
selected_points.points.extend(additional_selected_points);
|
||||||
|
|
||||||
@@ -387,27 +387,39 @@ impl PathToolData {
|
|||||||
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position - selected_points.offset);
|
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position - selected_points.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drag(&mut self, shift: bool, alt: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
fn update_colinear(&mut self, shift: bool, alt: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> bool {
|
||||||
// Check if the alt key has just been pressed
|
// Check if the alt key has just been pressed
|
||||||
if alt && !self.alt_debounce {
|
if alt && !self.alt_debounce {
|
||||||
self.opposing_handle_lengths = None;
|
self.opposing_handle_lengths = None;
|
||||||
shape_editor.toggle_colinear_handles_state_on_selected(responses);
|
let colinear = self.selection_status.angle().map_or(false, |angle| match angle {
|
||||||
|
ManipulatorAngle::Colinear => true,
|
||||||
|
ManipulatorAngle::Free => false,
|
||||||
|
ManipulatorAngle::Mixed => false,
|
||||||
|
});
|
||||||
|
if colinear {
|
||||||
|
shape_editor.disable_colinear_handles_state_on_selected(&document.metadata, &document.network, responses);
|
||||||
|
} else {
|
||||||
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||||
|
}
|
||||||
|
self.alt_debounce = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
self.alt_debounce = alt;
|
self.alt_debounce = alt;
|
||||||
|
|
||||||
if shift {
|
if shift {
|
||||||
if self.opposing_handle_lengths.is_none() {
|
if self.opposing_handle_lengths.is_none() {
|
||||||
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(&document.network));
|
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
|
||||||
}
|
}
|
||||||
} else if let Some(opposing_handle_lengths) = &self.opposing_handle_lengths {
|
|
||||||
shape_editor.reset_opposing_handle_lengths(&document.network, opposing_handle_lengths, responses);
|
|
||||||
self.opposing_handle_lengths = None;
|
|
||||||
}
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drag(&mut self, equidistant: bool, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
// Move the selected points with the mouse
|
// Move the selected points with the mouse
|
||||||
let previous_mouse = document.metadata.document_to_viewport.transform_point2(self.previous_mouse_position);
|
let previous_mouse = document.metadata.document_to_viewport.transform_point2(self.previous_mouse_position);
|
||||||
let snapped_delta = shape_editor.snap(&mut self.snap_manager, document, input, previous_mouse);
|
let snapped_delta = shape_editor.snap(&mut self.snap_manager, document, input, previous_mouse);
|
||||||
shape_editor.move_selected_points(&document.network, &document.metadata, snapped_delta, shift, responses);
|
let handle_lengths = if equidistant { None } else { self.opposing_handle_lengths.take() };
|
||||||
|
shape_editor.move_selected_points(handle_lengths, &document, snapped_delta, equidistant, responses);
|
||||||
self.previous_mouse_position += document.metadata.document_to_viewport.inverse().transform_vector2(snapped_delta);
|
self.previous_mouse_position += document.metadata.document_to_viewport.inverse().transform_vector2(snapped_delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,7 +443,6 @@ impl Fsm for PathToolFsmState {
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||||
// This can happen in any state (which is why we return self)
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||||
@@ -503,7 +514,9 @@ impl Fsm for PathToolFsmState {
|
|||||||
(PathToolFsmState::Dragging, PathToolMessage::PointerMove { alt, shift }) => {
|
(PathToolFsmState::Dragging, PathToolMessage::PointerMove { alt, shift }) => {
|
||||||
let alt_state = input.keyboard.get(alt as usize);
|
let alt_state = input.keyboard.get(alt as usize);
|
||||||
let shift_state = input.keyboard.get(shift as usize);
|
let shift_state = input.keyboard.get(shift as usize);
|
||||||
tool_data.drag(shift_state, alt_state, shape_editor, document, input, responses);
|
if !tool_data.update_colinear(shift_state, alt_state, shape_editor, document, responses) {
|
||||||
|
tool_data.drag(shift_state, shape_editor, document, input, responses);
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let messages = [PathToolMessage::PointerOutsideViewport { alt, shift }.into(), PathToolMessage::PointerMove { alt, shift }.into()];
|
let messages = [PathToolMessage::PointerOutsideViewport { alt, shift }.into(), PathToolMessage::PointerMove { alt, shift }.into()];
|
||||||
@@ -521,9 +534,9 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
(PathToolFsmState::Dragging, PathToolMessage::PointerOutsideViewport { shift, .. }) => {
|
(PathToolFsmState::Dragging, PathToolMessage::PointerOutsideViewport { shift, .. }) => {
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
if let Some(delta) = tool_data.auto_panning.shift_viewport(input, responses) {
|
if tool_data.auto_panning.shift_viewport(input, responses).is_some() {
|
||||||
let shift_state = input.keyboard.get(shift as usize);
|
let shift_state = input.keyboard.get(shift as usize);
|
||||||
shape_editor.move_selected_points(&document.network, &document.metadata, -delta, shift_state, responses);
|
tool_data.drag(shift_state, shape_editor, document, input, responses);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathToolFsmState::Dragging
|
PathToolFsmState::Dragging
|
||||||
@@ -575,18 +588,16 @@ impl Fsm for PathToolFsmState {
|
|||||||
(_, PathToolMessage::DragStop { equidistant }) => {
|
(_, PathToolMessage::DragStop { equidistant }) => {
|
||||||
let equidistant = input.keyboard.get(equidistant as usize);
|
let equidistant = input.keyboard.get(equidistant as usize);
|
||||||
|
|
||||||
let nearest_point = shape_editor
|
let nearest_point = shape_editor.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD);
|
||||||
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
|
|
||||||
.map(|(_, nearest_point)| nearest_point);
|
|
||||||
|
|
||||||
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
|
if let Some((layer, nearest_point)) = nearest_point {
|
||||||
|
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
|
||||||
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !equidistant {
|
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
|
||||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
|
if clicked_selected {
|
||||||
if clicked_selected {
|
shape_editor.deselect_all_points();
|
||||||
shape_editor.deselect_all_points();
|
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
|
||||||
shape_editor.change_point_selection(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, false);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(OverlaysMessage::Draw);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -599,17 +610,17 @@ impl Fsm for PathToolFsmState {
|
|||||||
(_, PathToolMessage::Delete) => {
|
(_, PathToolMessage::Delete) => {
|
||||||
// Delete the selected points and clean up overlays
|
// Delete the selected points and clean up overlays
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.delete_selected_points(responses);
|
shape_editor.delete_selected_points(&document, responses);
|
||||||
responses.add(PathToolMessage::SelectionChanged);
|
responses.add(PathToolMessage::SelectionChanged);
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::BreakPath) => {
|
(_, PathToolMessage::BreakPath) => {
|
||||||
shape_editor.break_path_at_selected_point(&document.network, responses);
|
shape_editor.break_path_at_selected_point(document, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::DeleteAndBreakPath) => {
|
(_, PathToolMessage::DeleteAndBreakPath) => {
|
||||||
shape_editor.delete_point_and_break_path(&document.network, responses);
|
shape_editor.delete_point_and_break_path(document, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::FlipSmoothSharp) => {
|
(_, PathToolMessage::FlipSmoothSharp) => {
|
||||||
@@ -625,12 +636,12 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
(_, PathToolMessage::PointerMove { .. }) => self,
|
(_, PathToolMessage::PointerMove { .. }) => self,
|
||||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||||
shape_editor.move_selected_points(&document.network, &document.metadata, (delta_x, delta_y).into(), true, responses);
|
shape_editor.move_selected_points(tool_data.opposing_handle_lengths.take(), &document, (delta_x, delta_y).into(), true, responses);
|
||||||
|
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectAllAnchors) => {
|
(_, PathToolMessage::SelectAllAnchors) => {
|
||||||
shape_editor.select_all_anchors_in_selected_layers(&document.network);
|
shape_editor.select_all_anchors_in_selected_layers(document);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
@@ -641,13 +652,13 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
|
||||||
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(new_x, coordinates.y), layer);
|
shape_editor.reposition_control_point(&id, &document.network, &document.metadata, DVec2::new(new_x, coordinates.y), layer, responses);
|
||||||
}
|
}
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
|
||||||
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
|
||||||
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(coordinates.x, new_y), layer);
|
shape_editor.reposition_control_point(&id, &document.network, &document.metadata, DVec2::new(coordinates.x, new_y), layer, responses);
|
||||||
}
|
}
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
@@ -657,14 +668,14 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.set_colinear_handles_state_on_selected(true, responses);
|
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
|
||||||
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, &document.network);
|
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
|
responses.add(PathToolMessage::SelectionChanged);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
shape_editor.set_colinear_handles_state_on_selected(false, responses);
|
shape_editor.disable_colinear_handles_state_on_selected(&document.metadata, &document.network, responses);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
@@ -743,10 +754,11 @@ impl SelectionStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_multiple(&self) -> Option<&MultipleSelectedPoints> {
|
fn angle(&self) -> Option<ManipulatorAngle> {
|
||||||
match self {
|
match self {
|
||||||
SelectionStatus::Multiple(multiple) => Some(multiple),
|
Self::None => None,
|
||||||
_ => None,
|
Self::One(one) => Some(one.manipulator_angle),
|
||||||
|
Self::Multiple(one) => Some(one.manipulator_angle),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -775,33 +787,23 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
|
|||||||
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(subpaths) = get_subpaths(layer, document_network) else {
|
let Some(vector_data) = document_metadata.compute_modified_vector(layer, document_network) else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(colinear_manipulators) = get_colinear_manipulators(layer, document_network) else {
|
let Some(&point) = shape_state.selected_points().next() else {
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
let Some(point) = shape_state.selected_points().next() else {
|
let Some(local_position) = point.get_position(&vector_data) else {
|
||||||
return SelectionStatus::None;
|
|
||||||
};
|
|
||||||
let Some(manipulator) = get_manipulator_from_id(subpaths, point.group) else {
|
|
||||||
return SelectionStatus::None;
|
|
||||||
};
|
|
||||||
let Some(local_position) = point.manipulator_type.get_position(manipulator) else {
|
|
||||||
return SelectionStatus::None;
|
return SelectionStatus::None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
|
let coordinates = document_metadata.transform_to_document(layer).transform_point2(local_position);
|
||||||
let manipulator_angle = if colinear_manipulators.contains(&point.group) {
|
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
|
||||||
ManipulatorAngle::Colinear
|
|
||||||
} else {
|
|
||||||
ManipulatorAngle::Free
|
|
||||||
};
|
|
||||||
|
|
||||||
return SelectionStatus::One(SingleSelectedPoint {
|
return SelectionStatus::One(SingleSelectedPoint {
|
||||||
coordinates,
|
coordinates,
|
||||||
layer,
|
layer,
|
||||||
id: *point,
|
id: point,
|
||||||
manipulator_angle,
|
manipulator_angle,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -809,7 +811,7 @@ fn get_selection_status(document_network: &NodeNetwork, document_metadata: &Docu
|
|||||||
// Check to see if multiple manipulator groups are selected
|
// Check to see if multiple manipulator groups are selected
|
||||||
if total_selected_points > 1 {
|
if total_selected_points > 1 {
|
||||||
return SelectionStatus::Multiple(MultipleSelectedPoints {
|
return SelectionStatus::Multiple(MultipleSelectedPoints {
|
||||||
manipulator_angle: shape_state.selected_manipulator_angles(document_network),
|
manipulator_angle: shape_state.selected_manipulator_angles(document_network, document_metadata),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
|
use crate::consts::HIDE_HANDLE_DISTANCE;
|
||||||
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
use crate::consts::LINE_ROTATE_SNAP_ANGLE;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
|
use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
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::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_subpaths;
|
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::utility_functions::should_extend;
|
use crate::messages::tool::common_functionality::utility_functions::should_extend;
|
||||||
|
|
||||||
|
use bezier_rs::{Bezier, BezierHandles};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_core::uuid::{generate_uuid, ManipulatorGroupId};
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
use graphene_core::vector::{PointId, VectorModificationType};
|
||||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
use graphene_std::vector::{HandleId, SegmentId};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PenTool {
|
pub struct PenTool {
|
||||||
@@ -187,310 +188,175 @@ impl ToolTransition for PenTool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct ModifierState {
|
struct ModifierState {
|
||||||
snap_angle: bool,
|
snap_angle: bool,
|
||||||
lock_angle: bool,
|
lock_angle: bool,
|
||||||
break_handle: bool,
|
break_handle: bool,
|
||||||
}
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct LastPoint {
|
||||||
|
id: PointId,
|
||||||
|
pos: DVec2,
|
||||||
|
in_segment: Option<SegmentId>,
|
||||||
|
handle_start: DVec2,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct PenToolData {
|
struct PenToolData {
|
||||||
weight: f64,
|
|
||||||
layer: Option<LayerNodeIdentifier>,
|
layer: Option<LayerNodeIdentifier>,
|
||||||
subpath_index: usize,
|
|
||||||
snap_manager: SnapManager,
|
snap_manager: SnapManager,
|
||||||
colinear_handles: bool,
|
latest_points: Vec<LastPoint>,
|
||||||
// Indicates that curve extension is occurring from the first point, rather than (more commonly) the last point
|
point_index: usize,
|
||||||
from_start: bool,
|
handle_end: Option<DVec2>,
|
||||||
|
next_point: DVec2,
|
||||||
|
next_handle_start: DVec2,
|
||||||
|
|
||||||
|
g1_continuous: bool,
|
||||||
|
|
||||||
angle: f64,
|
angle: f64,
|
||||||
auto_panning: AutoPanning,
|
auto_panning: AutoPanning,
|
||||||
|
modifiers: ModifierState,
|
||||||
}
|
}
|
||||||
impl PenToolData {
|
impl PenToolData {
|
||||||
fn extend_subpath(&mut self, layer: LayerNodeIdentifier, subpath_index: usize, from_start: bool, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
fn latest_point(&self) -> Option<&LastPoint> {
|
||||||
self.layer = Some(layer);
|
self.latest_points.get(self.point_index)
|
||||||
self.from_start = from_start;
|
|
||||||
self.subpath_index = subpath_index;
|
|
||||||
|
|
||||||
let Some(subpaths) = get_subpaths(layer, &document.network) else { return };
|
|
||||||
let manipulator_groups = subpaths[subpath_index].manipulator_groups();
|
|
||||||
let first_or_last = if from_start { manipulator_groups.first() } else { manipulator_groups.last() };
|
|
||||||
let Some(last_handle) = first_or_last else { return };
|
|
||||||
let id = last_handle.id;
|
|
||||||
let modification = VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false };
|
|
||||||
|
|
||||||
// Stop the handles on the first point from being colinear
|
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_new_path(
|
fn latest_point_mut(&mut self) -> Option<&mut LastPoint> {
|
||||||
&mut self,
|
self.latest_points.get_mut(self.point_index)
|
||||||
document: &DocumentMessageHandler,
|
}
|
||||||
line_weight: f64,
|
|
||||||
stroke_color: Option<Color>,
|
|
||||||
fill_color: Option<Color>,
|
|
||||||
input: &InputPreprocessorMessageHandler,
|
|
||||||
responses: &mut VecDeque<Message>,
|
|
||||||
) {
|
|
||||||
let parent = document.new_layer_parent(true);
|
|
||||||
// Deselect layers because we are now creating a new layer
|
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
|
||||||
|
|
||||||
// Get the position and set properties
|
fn add_point(&mut self, point: LastPoint) {
|
||||||
let transform = document.metadata().transform_to_document(parent);
|
self.point_index = (self.point_index + 1).min(self.latest_points.len());
|
||||||
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
|
self.latest_points.truncate(self.point_index);
|
||||||
let snapped = self.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
self.latest_points.push(point);
|
||||||
let start_position = transform.inverse().transform_point2(snapped.snapped_point_document);
|
|
||||||
self.snap_manager.update_indicator(snapped);
|
|
||||||
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);
|
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), parent, responses);
|
|
||||||
self.layer = Some(layer);
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(stroke_color, line_weight),
|
|
||||||
});
|
|
||||||
|
|
||||||
self.from_start = false;
|
|
||||||
self.subpath_index = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
|
/// If the user places the anchor on top of the previous anchor, it becomes sharp and the outgoing handle may be dragged.
|
||||||
fn bend_from_previous_point(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
fn bend_from_previous_point(&mut self, snap_data: SnapData, transform: DAffine2) {
|
||||||
(|| -> Option<()> {
|
self.g1_continuous = true;
|
||||||
// Get subpath
|
|
||||||
let layer = self.layer?;
|
|
||||||
let subpath = &get_subpaths(layer, &document.network)?[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 transform = document.metadata.document_to_viewport * transform;
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: previous_anchor },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop the handles on the last point from being colinear
|
|
||||||
let id = previous_manipulator_group.id;
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer,
|
|
||||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
self.colinear_handles = false;
|
|
||||||
|
|
||||||
None
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finish_placing_handle(&mut self, document: &DocumentMessageHandler, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
|
||||||
// Get subpath
|
|
||||||
let layer = self.layer?;
|
|
||||||
let subpath = &get_subpaths(layer, &document.network)?[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 transform = document.metadata.document_to_viewport * transform;
|
|
||||||
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,
|
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: last_in },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop the handles on the first point from being colinear
|
|
||||||
let id = first_manipulator_group.id;
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer,
|
|
||||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the point that has just been placed
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer,
|
|
||||||
modification: VectorDataModification::RemoveManipulatorGroup { id: last_manipulator_group.id },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Push a close path node
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer,
|
|
||||||
modification: VectorDataModification::SetClosed { index: 0, closed: true },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
|
||||||
|
|
||||||
// Clean up tool data
|
|
||||||
self.layer = 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.add(add_manipulator_group(self.layer, self.from_start, bezier_rs::ManipulatorGroup::new_anchor(out_handle)));
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(PenToolFsmState::PlacingAnchor)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drag_handle(&mut self, mut snap_data: SnapData, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
|
||||||
let document = snap_data.document;
|
let document = snap_data.document;
|
||||||
// Get subpath
|
self.next_handle_start = self.next_point;
|
||||||
let subpath = &get_subpaths(self.layer?, &document.network)?[self.subpath_index];
|
|
||||||
|
|
||||||
// Get the last manipulator group
|
// Break the control
|
||||||
let manipulator_groups = subpath.manipulator_groups();
|
let Some(last_pos) = self.latest_point().map(|point| point.pos) else { return };
|
||||||
let last_manipulator_group = if self.from_start { manipulator_groups.first()? } else { manipulator_groups.last()? };
|
let transform = document.metadata.document_to_viewport * transform;
|
||||||
|
let on_top = transform.transform_point2(self.next_point).distance_squared(transform.transform_point2(last_pos)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||||
// Get correct handle types
|
if on_top {
|
||||||
let inwards_handle = if self.from_start { SelectedType::OutHandle } else { SelectedType::InHandle };
|
if let Some(point) = self.latest_point_mut() {
|
||||||
let outwards_handle = if self.from_start { SelectedType::InHandle } else { SelectedType::OutHandle };
|
point.in_segment = None;
|
||||||
|
}
|
||||||
// Get manipulator points
|
self.handle_end = None;
|
||||||
let last_anchor = last_manipulator_group.anchor;
|
|
||||||
|
|
||||||
let colinear = !modifiers.break_handle && self.colinear_handles;
|
|
||||||
|
|
||||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
|
||||||
let position = self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, colinear, mouse, Some(last_anchor), false);
|
|
||||||
if !position.is_finite() {
|
|
||||||
return Some(PenToolFsmState::DraggingHandle);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update points on current segment (to show preview of new handle)
|
fn finish_placing_handle(&mut self, snap_data: SnapData, transform: DAffine2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||||
let point = ManipulatorPointId::new(last_manipulator_group.id, outwards_handle);
|
let document = snap_data.document;
|
||||||
responses.add(GraphOperationMessage::Vector {
|
let next_handle_start = self.next_handle_start;
|
||||||
layer: self.layer?,
|
let handle_start = self.latest_point()?.handle_start;
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
let mouse = snap_data.input.mouse.position;
|
||||||
|
let Some(handle_end) = self.handle_end else {
|
||||||
|
self.handle_end = Some(next_handle_start);
|
||||||
|
self.place_anchor(snap_data, transform, mouse, responses);
|
||||||
|
self.latest_point_mut()?.handle_start = next_handle_start;
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let next_point = self.next_point;
|
||||||
|
self.place_anchor(snap_data, transform, mouse, responses);
|
||||||
|
let handles = [handle_start - self.latest_point()?.pos, handle_end - next_point].map(Some);
|
||||||
|
|
||||||
|
// Get close path
|
||||||
|
let mut end = None;
|
||||||
|
let layer = self.layer?;
|
||||||
|
let vector_data = document.metadata.compute_modified_vector(layer, &document.network)?;
|
||||||
|
let start = self.latest_point()?.id;
|
||||||
|
let transform = document.metadata.document_to_viewport * transform;
|
||||||
|
for id in vector_data.single_connected_points().filter(|&point| point != start) {
|
||||||
|
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
|
||||||
|
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
|
||||||
|
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
|
||||||
|
if transformed_distance_between_squared < snap_point_tolerance_squared {
|
||||||
|
end = Some(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let close_subpath = end.is_some();
|
||||||
|
|
||||||
|
// Generate new point if not closing
|
||||||
|
let end = end.unwrap_or_else(|| {
|
||||||
|
let end = PointId::generate();
|
||||||
|
let modification_type = VectorModificationType::InsertPoint { id: end, position: next_point };
|
||||||
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
|
end
|
||||||
});
|
});
|
||||||
|
|
||||||
// Place the previous anchor's in handle at the opposing position
|
let points = [start, end];
|
||||||
if colinear {
|
let id = SegmentId::generate();
|
||||||
// Could also be written as `last_anchor.position * 2 - pos` but this way avoids overflow/underflow better
|
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
|
||||||
let position = last_anchor - (position - last_anchor);
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
let point = ManipulatorPointId::new(last_manipulator_group.id, inwards_handle);
|
|
||||||
|
// Mirror
|
||||||
|
if let Some(last_segment) = self.latest_point().and_then(|point| point.in_segment) {
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer: self.layer?,
|
layer,
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
modification_type: VectorModificationType::SetG1Continuous {
|
||||||
|
handles: [HandleId::end(last_segment), HandleId::primary(id)],
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if !close_subpath {
|
||||||
|
self.add_point(LastPoint {
|
||||||
|
id: end,
|
||||||
|
pos: next_point,
|
||||||
|
in_segment: self.g1_continuous.then_some(id),
|
||||||
|
handle_start: next_handle_start,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(if close_subpath { PenToolFsmState::Ready } else { PenToolFsmState::PlacingAnchor })
|
||||||
|
}
|
||||||
|
|
||||||
// Update the colinear handles status of the currently modifying point
|
fn drag_handle(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||||
let id = last_manipulator_group.id;
|
let colinear = !self.modifiers.break_handle && self.handle_end.is_some();
|
||||||
responses.add(GraphOperationMessage::Vector {
|
self.next_handle_start = self.compute_snapped_angle(snap_data, transform, colinear, mouse, Some(self.next_point), false);
|
||||||
layer: self.layer?,
|
if let Some(handle_end) = self.handle_end.as_mut().filter(|_| colinear) {
|
||||||
modification: VectorDataModification::SetManipulatorColinearHandlesState { id, colinear },
|
*handle_end = self.next_point * 2. - self.next_handle_start;
|
||||||
});
|
self.g1_continuous = true;
|
||||||
|
} else {
|
||||||
|
self.g1_continuous = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
Some(PenToolFsmState::DraggingHandle)
|
Some(PenToolFsmState::DraggingHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn place_anchor(&mut self, mut snap_data: SnapData, transform: DAffine2, mouse: DVec2, modifiers: ModifierState, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
fn place_anchor(&mut self, snap_data: SnapData, transform: DAffine2, mouse: DVec2, responses: &mut VecDeque<Message>) -> Option<PenToolFsmState> {
|
||||||
let document = snap_data.document;
|
let relative = self.latest_point().map(|point| point.pos);
|
||||||
// Get subpath
|
self.next_point = self.compute_snapped_angle(snap_data, transform, false, mouse, relative, true);
|
||||||
let layer = self.layer?;
|
if let Some(handle_end) = self.handle_end.as_mut() {
|
||||||
let subpath = &get_subpaths(layer, &document.network)?[self.subpath_index];
|
*handle_end = self.next_point;
|
||||||
|
self.next_handle_start = self.next_point;
|
||||||
// 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 previous_anchor = previous_manipulator_group.map(|group| group.anchor);
|
|
||||||
|
|
||||||
let pos = if let Some(last_anchor) = previous_anchor.filter(|&a| mouse.distance_squared(transform.transform_point2(a)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2)) {
|
|
||||||
// Snap to the previously placed point (to show break control)
|
|
||||||
last_anchor
|
|
||||||
} else if mouse.distance_squared(transform.transform_point2(first_anchor)) < crate::consts::SNAP_POINT_TOLERANCE.powi(2) {
|
|
||||||
// Snap to the first point (to show close path)
|
|
||||||
first_anchor
|
|
||||||
} else {
|
|
||||||
snap_data.manipulators = vec![(self.layer?, last_manipulator_group.id)];
|
|
||||||
self.compute_snapped_angle(snap_data, transform, modifiers.lock_angle, modifiers.snap_angle, false, mouse, previous_anchor, true)
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position: pos },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
Some(PenToolFsmState::PlacingAnchor)
|
Some(PenToolFsmState::PlacingAnchor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Snap the angle of the line from relative to position if the key is pressed.
|
/// Snap the angle of the line from relative to position if the key is pressed.
|
||||||
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, lock_angle: bool, snap_angle: bool, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
fn compute_snapped_angle(&mut self, snap_data: SnapData, transform: DAffine2, colinear: bool, mouse: DVec2, relative: Option<DVec2>, neighbor: bool) -> DVec2 {
|
||||||
|
let ModifierState { snap_angle, lock_angle, .. } = self.modifiers;
|
||||||
let document = snap_data.document;
|
let document = snap_data.document;
|
||||||
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
let mut document_pos = document.metadata.document_to_viewport.inverse().transform_point2(mouse);
|
||||||
let snap = &mut self.snap_manager;
|
let snap = &mut self.snap_manager;
|
||||||
|
|
||||||
let neighbors = relative.filter(|_| neighbor).map_or(Vec::new(), |neighbor| vec![neighbor]);
|
let neighbors = relative.filter(|_| neighbor).map_or(Vec::new(), |neighbor| vec![neighbor]);
|
||||||
|
|
||||||
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)).filter(|_| snap_angle || lock_angle) {
|
if let Some(relative) = relative
|
||||||
|
.map(|layer| transform.transform_point2(layer))
|
||||||
|
.filter(|&relative| (snap_angle || lock_angle) && (relative - document_pos).length_squared() > f64::EPSILON * 100.)
|
||||||
|
{
|
||||||
let resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
let resolution = LINE_ROTATE_SNAP_ANGLE.to_radians();
|
||||||
|
|
||||||
let angle = if lock_angle {
|
let angle = if lock_angle {
|
||||||
@@ -540,56 +406,11 @@ impl PenToolData {
|
|||||||
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)) {
|
if let Some(relative) = relative.map(|layer| transform.transform_point2(layer)) {
|
||||||
if (relative - document_pos) != DVec2::ZERO {
|
if (relative - document_pos) != DVec2::ZERO {
|
||||||
self.angle = -(relative - document_pos).angle_between(DVec2::X)
|
self.angle = -(relative - document_pos).angle_between(DVec2::X)
|
||||||
} else {
|
|
||||||
self.angle = 0.0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transform.inverse().transform_point2(document_pos)
|
transform.inverse().transform_point2(document_pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish_transaction(&mut self, fsm: PenToolFsmState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Option<DocumentMessage> {
|
|
||||||
// Get subpath
|
|
||||||
let subpath = &get_subpaths(self.layer?, &document.network)?[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 {
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
|
||||||
layer: self.layer?,
|
|
||||||
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: self.layer?,
|
|
||||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(DocumentMessage::CommitTransaction)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for PenToolFsmState {
|
impl Fsm for PenToolFsmState {
|
||||||
@@ -628,9 +449,52 @@ impl Fsm for PenToolFsmState {
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
(PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||||
path_overlays(document, shape_editor, &mut overlay_context);
|
path_overlays(document, shape_editor, &mut overlay_context);
|
||||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||||
|
let transform = document.metadata.document_to_viewport * transform;
|
||||||
|
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
||||||
|
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||||
|
let bezier = Bezier {
|
||||||
|
start,
|
||||||
|
handles,
|
||||||
|
end: tool_data.next_point,
|
||||||
|
};
|
||||||
|
overlay_context.outline_bezier(bezier, transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
let valid = |point: DVec2, handle: DVec2| point.distance_squared(handle) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||||
|
let next_point = transform.transform_point2(tool_data.next_point);
|
||||||
|
let next_handle_start = transform.transform_point2(tool_data.next_handle_start);
|
||||||
|
overlay_context.line(next_point, next_handle_start);
|
||||||
|
let start = tool_data.latest_point().map(|point| transform.transform_point2(point.pos));
|
||||||
|
|
||||||
|
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
||||||
|
let handle_end = tool_data.handle_end.map(|point| transform.transform_point2(point));
|
||||||
|
|
||||||
|
if let (Some(start), Some(handle_start), Some(handle_end)) = (start, handle_start, handle_end) {
|
||||||
|
overlay_context.line(start, handle_start);
|
||||||
|
overlay_context.line(next_point, handle_end);
|
||||||
|
|
||||||
|
path_overlays(document, shape_editor, &mut overlay_context);
|
||||||
|
|
||||||
|
if self == PenToolFsmState::DraggingHandle && valid(next_point, handle_end) {
|
||||||
|
overlay_context.manipulator_handle(handle_end, false);
|
||||||
|
}
|
||||||
|
if valid(start, handle_start) {
|
||||||
|
overlay_context.manipulator_handle(handle_start, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
path_overlays(document, shape_editor, &mut overlay_context);
|
||||||
|
}
|
||||||
|
if self == PenToolFsmState::DraggingHandle && valid(next_point, next_handle_start) {
|
||||||
|
overlay_context.manipulator_handle(next_handle_start, false);
|
||||||
|
}
|
||||||
|
overlay_context.manipulator_anchor(next_point, false, None);
|
||||||
|
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@@ -644,46 +508,71 @@ impl Fsm for PenToolFsmState {
|
|||||||
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
(PenToolFsmState::Ready, PenToolMessage::DragStart) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
// Prevent the initial point from having a colinear in handle while dragging the out handle
|
|
||||||
tool_data.colinear_handles = false;
|
|
||||||
|
|
||||||
// Perform extension of an existing path
|
// Perform extension of an existing path
|
||||||
if let Some((layer, subpath_index, from_start)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
if let Some((layer, point, position)) = should_extend(document, input.mouse.position, crate::consts::SNAP_POINT_TOLERANCE) {
|
||||||
tool_data.extend_subpath(layer, subpath_index, from_start, document, responses);
|
tool_data.add_point(LastPoint {
|
||||||
|
id: point,
|
||||||
|
pos: position,
|
||||||
|
in_segment: None,
|
||||||
|
handle_start: position,
|
||||||
|
});
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
tool_data.next_point = position;
|
||||||
|
tool_data.next_handle_start = position;
|
||||||
} else {
|
} else {
|
||||||
tool_data.create_new_path(
|
// New path layer
|
||||||
document,
|
let nodes = {
|
||||||
tool_options.line_weight,
|
let node_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
||||||
tool_options.stroke.active_color(),
|
HashMap::from([(NodeId(0), node_type.to_document_node_default_inputs([], Default::default()))])
|
||||||
tool_options.fill.active_color(),
|
};
|
||||||
input,
|
|
||||||
responses,
|
let parent = document.new_layer_parent(true);
|
||||||
);
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);
|
||||||
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
|
// Generate first point
|
||||||
|
let id = PointId::generate();
|
||||||
|
let transform = document.metadata().transform_to_document(parent);
|
||||||
|
let point = SnapCandidatePoint::handle(document.metadata.document_to_viewport.inverse().transform_point2(input.mouse.position));
|
||||||
|
let snapped = tool_data.snap_manager.free_snap(&SnapData::new(document, input), &point, None, false);
|
||||||
|
let pos = transform.inverse().transform_point2(snapped.snapped_point_document);
|
||||||
|
let modification_type = VectorModificationType::InsertPoint { id, position: pos };
|
||||||
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
tool_data.add_point(LastPoint {
|
||||||
|
id,
|
||||||
|
pos,
|
||||||
|
in_segment: None,
|
||||||
|
handle_start: pos,
|
||||||
|
});
|
||||||
|
tool_data.next_point = pos;
|
||||||
|
tool_data.next_handle_start = pos;
|
||||||
}
|
}
|
||||||
|
tool_data.handle_end = None;
|
||||||
|
|
||||||
// Enter the dragging handle state while the mouse is held down, allowing the user to move the mouse and position the handle
|
// 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::DraggingHandle
|
||||||
}
|
}
|
||||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
(PenToolFsmState::PlacingAnchor, PenToolMessage::DragStart) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
if tool_data.handle_end.is_some() {
|
||||||
tool_data.bend_from_previous_point(document, transform, responses);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
}
|
||||||
|
tool_data.bend_from_previous_point(SnapData::new(document, input), transform);
|
||||||
PenToolFsmState::DraggingHandle
|
PenToolFsmState::DraggingHandle
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => {
|
(PenToolFsmState::DraggingHandle, PenToolMessage::DragStop) => tool_data
|
||||||
tool_data.colinear_handles = true;
|
.finish_placing_handle(SnapData::new(document, input), transform, responses)
|
||||||
tool_data.finish_placing_handle(document, transform, responses).unwrap_or(PenToolFsmState::PlacingAnchor)
|
.unwrap_or(PenToolFsmState::PlacingAnchor),
|
||||||
}
|
|
||||||
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
(PenToolFsmState::DraggingHandle, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||||
let modifiers = ModifierState {
|
tool_data.modifiers = ModifierState {
|
||||||
snap_angle: input.keyboard.key(snap_angle),
|
snap_angle: input.keyboard.key(snap_angle),
|
||||||
lock_angle: input.keyboard.key(lock_angle),
|
lock_angle: input.keyboard.key(lock_angle),
|
||||||
break_handle: input.keyboard.key(break_handle),
|
break_handle: input.keyboard.key(break_handle),
|
||||||
};
|
};
|
||||||
let snap_data = SnapData::new(document, input);
|
let snap_data = SnapData::new(document, input);
|
||||||
|
|
||||||
let state = tool_data
|
let state = tool_data.drag_handle(snap_data, transform, input.mouse.position, responses).unwrap_or(PenToolFsmState::Ready);
|
||||||
.drag_handle(snap_data, transform, input.mouse.position, modifiers, responses)
|
|
||||||
.unwrap_or(PenToolFsmState::Ready);
|
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let messages = [
|
let messages = [
|
||||||
@@ -695,13 +584,13 @@ impl Fsm for PenToolFsmState {
|
|||||||
state
|
state
|
||||||
}
|
}
|
||||||
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
(PenToolFsmState::PlacingAnchor, PenToolMessage::PointerMove { snap_angle, break_handle, lock_angle }) => {
|
||||||
let modifiers = ModifierState {
|
tool_data.modifiers = ModifierState {
|
||||||
snap_angle: input.keyboard.key(snap_angle),
|
snap_angle: input.keyboard.key(snap_angle),
|
||||||
lock_angle: input.keyboard.key(lock_angle),
|
lock_angle: input.keyboard.key(lock_angle),
|
||||||
break_handle: input.keyboard.key(break_handle),
|
break_handle: input.keyboard.key(break_handle),
|
||||||
};
|
};
|
||||||
let state = tool_data
|
let state = tool_data
|
||||||
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, modifiers, responses)
|
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses)
|
||||||
.unwrap_or(PenToolFsmState::Ready);
|
.unwrap_or(PenToolFsmState::Ready);
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
@@ -741,11 +630,10 @@ impl Fsm for PenToolFsmState {
|
|||||||
state
|
state
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
|
||||||
// Abort or commit the transaction to the undo history
|
|
||||||
let message = tool_data.finish_transaction(self, document, responses).unwrap_or(DocumentMessage::AbortTransaction);
|
|
||||||
responses.add(message);
|
|
||||||
|
|
||||||
tool_data.layer = None;
|
tool_data.layer = None;
|
||||||
|
tool_data.handle_end = None;
|
||||||
|
tool_data.latest_points.clear();
|
||||||
|
tool_data.point_index = 0;
|
||||||
tool_data.snap_manager.cleanup(responses);
|
tool_data.snap_manager.cleanup(responses);
|
||||||
|
|
||||||
PenToolFsmState::Ready
|
PenToolFsmState::Ready
|
||||||
@@ -755,12 +643,23 @@ impl Fsm for PenToolFsmState {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Undo) => tool_data
|
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Undo) => {
|
||||||
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, ModifierState::default(), responses)
|
if tool_data.point_index > 0 {
|
||||||
.unwrap_or(PenToolFsmState::PlacingAnchor),
|
tool_data.point_index -= 1;
|
||||||
(_, PenToolMessage::Redo) => tool_data
|
tool_data
|
||||||
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, ModifierState::default(), responses)
|
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses)
|
||||||
.unwrap_or(PenToolFsmState::PlacingAnchor),
|
.unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||||
|
} else {
|
||||||
|
responses.add(PenToolMessage::Abort);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, PenToolMessage::Redo) => {
|
||||||
|
tool_data.point_index = (tool_data.point_index + 1).min(tool_data.latest_points.len().saturating_sub(1));
|
||||||
|
tool_data
|
||||||
|
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, responses)
|
||||||
|
.unwrap_or(PenToolFsmState::PlacingAnchor)
|
||||||
|
}
|
||||||
_ => self,
|
_ => self,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,16 +699,3 @@ impl Fsm for PenToolFsmState {
|
|||||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a [ManipulatorGroup] to the current layer via a [GraphOperationMessage].
|
|
||||||
fn add_manipulator_group(layer: Option<LayerNodeIdentifier>, from_start: bool, manipulator_group: bezier_rs::ManipulatorGroup<ManipulatorGroupId>) -> Message {
|
|
||||||
let Some(layer) = layer 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()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
@@ -7,9 +8,8 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -244,11 +244,34 @@ impl Fsm for PolygonToolFsmState {
|
|||||||
polygon_data.start(document, input);
|
polygon_data.start(document, input);
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
let subpath = match tool_options.polygon_type {
|
let nodes = {
|
||||||
PolygonType::Convex => bezier_rs::Subpath::new_regular_polygon(DVec2::ZERO, tool_options.vertices as u64, 1.),
|
let node = match tool_options.polygon_type {
|
||||||
PolygonType::Star => bezier_rs::Subpath::new_star_polygon(DVec2::ZERO, tool_options.vertices as u64, 1., 0.5),
|
PolygonType::Convex => resolve_document_node_type("Regular Polygon")
|
||||||
|
.expect("Regular Polygon node does not exist")
|
||||||
|
.to_document_node_default_inputs(
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices as u32), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
||||||
|
],
|
||||||
|
Default::default(),
|
||||||
|
),
|
||||||
|
PolygonType::Star => resolve_document_node_type("Star").expect("Star node does not exist").to_document_node_default_inputs(
|
||||||
|
[
|
||||||
|
None,
|
||||||
|
Some(NodeInput::value(TaggedValue::U32(tool_options.vertices as u32), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(0.5), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::F64(0.25), false)),
|
||||||
|
],
|
||||||
|
Default::default(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
HashMap::from([(NodeId(0), node)])
|
||||||
};
|
};
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(false), responses);
|
||||||
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
polygon_data.layer = Some(layer);
|
polygon_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
@@ -258,22 +281,19 @@ impl Fsm for PolygonToolFsmState {
|
|||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let fill_color = tool_options.fill.active_color();
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
|
||||||
});
|
|
||||||
|
|
||||||
PolygonToolFsmState::Drawing
|
PolygonToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(PolygonToolFsmState::Drawing, PolygonToolMessage::PointerMove { center, lock_ratio }) => {
|
(PolygonToolFsmState::Drawing, PolygonToolMessage::PointerMove { center, lock_ratio }) => {
|
||||||
if let Some(message) = polygon_data.calculate_transform(document, input, center, lock_ratio, false) {
|
if let Some([start, end]) = tool_data.data.calculate_points(document, input, center, lock_ratio) {
|
||||||
responses.add(message);
|
if let Some(layer) = tool_data.data.layer {
|
||||||
|
// TODO: make the scale impact the polygon/star node - we need to determine how to allow the polygon node to make irregular shapes
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(end - start, 0., (start + end) / 2.),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::{graph_operation::utility_types::TransformIn, overlays::utility_types::OverlayContext};
|
use crate::messages::portfolio::document::{graph_operation::utility_types::TransformIn, overlays::utility_types::OverlayContext};
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
@@ -6,9 +7,8 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use crate::messages::tool::common_functionality::resize::Resize;
|
use crate::messages::tool::common_functionality::resize::Resize;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapData;
|
use crate::messages::tool::common_functionality::snapping::SnapData;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -205,11 +205,20 @@ impl Fsm for RectangleToolFsmState {
|
|||||||
(RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => {
|
(RectangleToolFsmState::Ready, RectangleToolMessage::DragStart) => {
|
||||||
shape_data.start(document, input);
|
shape_data.start(document, input);
|
||||||
|
|
||||||
let subpath = bezier_rs::Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![subpath], NodeId(generate_uuid()), document.new_layer_parent(true), responses);
|
let nodes = {
|
||||||
|
let node_type = resolve_document_node_type("Rectangle").expect("Rectangle node does not exist");
|
||||||
|
let node = node_type.to_document_node_default_inputs(
|
||||||
|
[None, Some(NodeInput::value(TaggedValue::F64(1.), false)), Some(NodeInput::value(TaggedValue::F64(1.), false))],
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
HashMap::from([(NodeId(0), node)])
|
||||||
|
};
|
||||||
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, document.new_layer_parent(true), responses);
|
||||||
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||||
shape_data.layer = Some(layer);
|
shape_data.layer = Some(layer);
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
@@ -219,22 +228,19 @@ impl Fsm for RectangleToolFsmState {
|
|||||||
skip_rerender: false,
|
skip_rerender: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
let fill_color = tool_options.fill.active_color();
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
|
||||||
layer,
|
|
||||||
fill: if let Some(color) = fill_color { Fill::Solid(color) } else { Fill::None },
|
|
||||||
});
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_options.line_weight),
|
|
||||||
});
|
|
||||||
|
|
||||||
RectangleToolFsmState::Drawing
|
RectangleToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
(RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => {
|
(RectangleToolFsmState::Drawing, RectangleToolMessage::PointerMove { center, lock_ratio }) => {
|
||||||
if let Some(message) = shape_data.calculate_transform(document, input, center, lock_ratio, false) {
|
if let Some([start, end]) = shape_data.calculate_points(document, input, center, lock_ratio) {
|
||||||
responses.add(message);
|
if let Some(layer) = shape_data.layer {
|
||||||
|
// TODO: make the scale impact the rect node
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: DAffine2::from_scale_angle_translation(end - start, 0., (start + end) / 2.),
|
||||||
|
transform_in: TransformIn::Viewport,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
|
|||||||
@@ -529,7 +529,7 @@ impl Fsm for SelectToolFsmState {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut selected: Vec<_> = document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata()).collect();
|
let mut selected: Vec<_> = document.selected_nodes.selected_visible_and_unlocked_layers(document.metadata()).collect();
|
||||||
let intersection_list = document.click_list(input.mouse.position, &document.network);
|
let intersection_list = document.click_list(input.mouse.position, &document.network).collect::<Vec<_>>();
|
||||||
let intersection = document.find_deepest(&intersection_list, &document.network);
|
let intersection = document.find_deepest(&intersection_list, &document.network);
|
||||||
|
|
||||||
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
// If the user is dragging the bounding box bounds, go into ResizingBounds mode.
|
||||||
@@ -1264,7 +1264,7 @@ fn edit_layer_deepest_manipulation(layer: LayerNodeIdentifier, document_network:
|
|||||||
if is_layer_fed_by_node_of_name(layer, document_network, "Text") {
|
if is_layer_fed_by_node_of_name(layer, document_network, "Text") {
|
||||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Text });
|
||||||
responses.add(TextToolMessage::EditSelected);
|
responses.add(TextToolMessage::EditSelected);
|
||||||
} else if is_layer_fed_by_node_of_name(layer, document_network, "Shape") {
|
} else if is_layer_fed_by_node_of_name(layer, document_network, "Path") {
|
||||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::DRAG_THRESHOLD;
|
use crate::consts::DRAG_THRESHOLD;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::VectorDataModification;
|
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||||
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::{value::TaggedValue, NodeId, NodeInput};
|
||||||
use graphene_core::uuid::generate_uuid;
|
use graphene_core::uuid::generate_uuid;
|
||||||
use graphene_core::vector::style::{Fill, Stroke};
|
|
||||||
use graphene_core::Color;
|
use graphene_core::Color;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@@ -217,17 +216,15 @@ impl Fsm for SplineToolFsmState {
|
|||||||
|
|
||||||
tool_data.weight = tool_options.line_weight;
|
tool_data.weight = tool_options.line_weight;
|
||||||
|
|
||||||
let layer = graph_modification_utils::new_vector_layer(vec![], NodeId(generate_uuid()), parent, responses);
|
let nodes = {
|
||||||
|
let node_type = resolve_document_node_type("Spline").expect("Spline node does not exist");
|
||||||
|
let node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VecDVec2(Vec::new()), false))], Default::default());
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
HashMap::from([(NodeId(0), node)])
|
||||||
layer,
|
};
|
||||||
fill: if let Some(color) = tool_options.fill.active_color() { Fill::Solid(color) } else { Fill::None },
|
let layer = graph_modification_utils::new_custom(NodeId(generate_uuid()), nodes, parent, responses);
|
||||||
});
|
tool_options.fill.apply_fill(layer, responses);
|
||||||
|
tool_options.stroke.apply_stroke(tool_data.weight, layer, responses);
|
||||||
responses.add(GraphOperationMessage::StrokeSet {
|
|
||||||
layer,
|
|
||||||
stroke: Stroke::new(tool_options.stroke.active_color(), tool_data.weight),
|
|
||||||
});
|
|
||||||
tool_data.layer = Some(layer);
|
tool_data.layer = Some(layer);
|
||||||
|
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
@@ -247,7 +244,7 @@ impl Fsm for SplineToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_spline(tool_data, true, responses);
|
update_spline(document, tool_data, true, responses);
|
||||||
|
|
||||||
SplineToolFsmState::Drawing
|
SplineToolFsmState::Drawing
|
||||||
}
|
}
|
||||||
@@ -260,7 +257,7 @@ impl Fsm for SplineToolFsmState {
|
|||||||
let pos = transform.inverse().transform_point2(snapped_position);
|
let pos = transform.inverse().transform_point2(snapped_position);
|
||||||
tool_data.next_point = pos;
|
tool_data.next_point = pos;
|
||||||
|
|
||||||
update_spline(tool_data, true, responses);
|
update_spline(document, tool_data, true, responses);
|
||||||
|
|
||||||
// Auto-panning
|
// Auto-panning
|
||||||
let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];
|
let messages = [SplineToolMessage::PointerOutsideViewport.into(), SplineToolMessage::PointerMove.into()];
|
||||||
@@ -283,7 +280,7 @@ impl Fsm for SplineToolFsmState {
|
|||||||
}
|
}
|
||||||
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
|
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
|
||||||
if tool_data.points.len() >= 2 {
|
if tool_data.points.len() >= 2 {
|
||||||
update_spline(tool_data, false, responses);
|
update_spline(document, tool_data, false, responses);
|
||||||
responses.add(DocumentMessage::CommitTransaction);
|
responses.add(DocumentMessage::CommitTransaction);
|
||||||
} else {
|
} else {
|
||||||
responses.add(DocumentMessage::AbortTransaction);
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
@@ -324,20 +321,17 @@ impl Fsm for SplineToolFsmState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_spline(tool_data: &SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
fn update_spline(document: &DocumentMessageHandler, tool_data: &SplineToolData, show_preview: bool, responses: &mut VecDeque<Message>) {
|
||||||
let mut points = tool_data.points.clone();
|
let mut points = tool_data.points.clone();
|
||||||
if show_preview {
|
if show_preview {
|
||||||
points.push(tool_data.next_point)
|
points.push(tool_data.next_point)
|
||||||
}
|
}
|
||||||
|
let value = TaggedValue::VecDVec2(points);
|
||||||
|
|
||||||
let subpath = bezier_rs::Subpath::new_cubic_spline(points);
|
let Some(layer) = tool_data.layer else { return };
|
||||||
|
|
||||||
let Some(layer) = tool_data.layer else {
|
let Some(node_id) = graph_modification_utils::NodeGraphLayer::new(layer, document.network()).upstream_node_id_from_name("Spline") else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
responses.add_front(NodeGraphMessage::SetInputValue { node_id, input_index: 1, value });
|
||||||
graph_modification_utils::set_manipulator_colinear_handles_state(subpath.manipulator_groups(), layer, true, responses);
|
|
||||||
let subpaths = vec![subpath];
|
|
||||||
let modification = VectorDataModification::UpdateSubpaths { subpaths };
|
|
||||||
responses.add_front(GraphOperationMessage::Vector { layer, modification });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,25 +313,6 @@ impl TextToolData {
|
|||||||
TextToolFsmState::Ready
|
TextToolFsmState::Ready
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounds(&self, text: &str, font_cache: &FontCache) -> Option<[DVec2; 2]> {
|
|
||||||
let editing_text = self.editing_text.as_ref()?;
|
|
||||||
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
|
|
||||||
let subpaths = graphene_core::text::to_path(text, buzz_face, editing_text.font_size, None);
|
|
||||||
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box());
|
|
||||||
let combined_bounds = bounds.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])]).unwrap_or_default();
|
|
||||||
Some(combined_bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fix_text_bounds(&self, new_text: &str, _document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) -> Option<()> {
|
|
||||||
responses.add(GraphOperationMessage::UpdateBounds {
|
|
||||||
layer: self.layer,
|
|
||||||
old_bounds: self.get_bounds(&self.editing_text.as_ref()?.text, font_cache)?,
|
|
||||||
new_bounds: self.get_bounds(new_text, font_cache)?,
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||||
@@ -429,7 +410,6 @@ impl Fsm for TextToolFsmState {
|
|||||||
TextToolFsmState::Editing
|
TextToolFsmState::Editing
|
||||||
}
|
}
|
||||||
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
|
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
|
||||||
tool_data.fix_text_bounds(&new_text, document, font_cache, responses);
|
|
||||||
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
responses.add(NodeGraphMessage::SetQualifiedInputValue {
|
||||||
node_id: graph_modification_utils::get_text_id(tool_data.layer, &document.network).unwrap(),
|
node_id: graph_modification_utils::get_text_id(tool_data.layer, &document.network).unwrap(),
|
||||||
input_index: 1,
|
input_index: 1,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::consts::SLOWING_DIVISOR;
|
|||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
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::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, Typing};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
|
||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
use crate::messages::tool::utility_types::{ToolData, ToolType};
|
||||||
|
|
||||||
@@ -69,16 +68,12 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if using_path_tool {
|
if using_path_tool {
|
||||||
if let Some(subpaths) = selected_layers.first().and_then(|&layer| graph_modification_utils::get_subpaths(layer, &document.network)) {
|
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.metadata.compute_modified_vector(layer, &document.network)) {
|
||||||
*selected.original_transforms = OriginalTransforms::default();
|
*selected.original_transforms = OriginalTransforms::default();
|
||||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||||
|
|
||||||
let mut point_count: usize = 0;
|
let mut point_count: usize = 0;
|
||||||
let get_location = |point: &ManipulatorPointId| {
|
let get_location = |point: &ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||||
graph_modification_utils::get_manipulator_from_id(subpaths, point.group)
|
|
||||||
.and_then(|manipulator_group| point.manipulator_type.get_position(manipulator_group))
|
|
||||||
.map(|position| viewspace.transform_point2(position))
|
|
||||||
};
|
|
||||||
let points = shape_editor.selected_points();
|
let points = shape_editor.selected_points();
|
||||||
|
|
||||||
*selected.pivot = points.filter_map(get_location).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
*selected.pivot = points.filter_map(get_location).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64;
|
||||||
|
|||||||
@@ -57,12 +57,14 @@ pub struct NodeRuntime {
|
|||||||
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
||||||
/// The current click targets for layer nodes.
|
/// The current click targets for layer nodes.
|
||||||
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||||
|
/// Vector data in Path nodes.
|
||||||
|
vector_modify: HashMap<NodeId, VectorData>,
|
||||||
/// The current upstream transforms for nodes.
|
/// The current upstream transforms for nodes.
|
||||||
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages passed from the editor thread to the node runtime thread.
|
/// Messages passed from the editor thread to the node runtime thread.
|
||||||
enum NodeRuntimeMessage {
|
pub enum NodeRuntimeMessage {
|
||||||
ExecutionRequest(ExecutionRequest),
|
ExecutionRequest(ExecutionRequest),
|
||||||
FontCacheUpdate(FontCache),
|
FontCacheUpdate(FontCache),
|
||||||
ImaginatePreferencesUpdate(ImaginatePreferences),
|
ImaginatePreferencesUpdate(ImaginatePreferences),
|
||||||
@@ -78,24 +80,25 @@ pub struct ExportConfig {
|
|||||||
pub size: DVec2,
|
pub size: DVec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ExecutionRequest {
|
pub struct ExecutionRequest {
|
||||||
execution_id: u64,
|
execution_id: u64,
|
||||||
graph: NodeNetwork,
|
graph: NodeNetwork,
|
||||||
render_config: RenderConfig,
|
render_config: RenderConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct ExecutionResponse {
|
pub struct ExecutionResponse {
|
||||||
execution_id: u64,
|
execution_id: u64,
|
||||||
result: Result<TaggedValue, String>,
|
result: Result<TaggedValue, String>,
|
||||||
responses: VecDeque<Message>,
|
responses: VecDeque<Message>,
|
||||||
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||||
|
new_vector_modify: HashMap<NodeId, VectorData>,
|
||||||
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||||
resolved_types: ResolvedDocumentNodeTypes,
|
resolved_types: ResolvedDocumentNodeTypes,
|
||||||
node_graph_errors: GraphErrors,
|
node_graph_errors: GraphErrors,
|
||||||
transform: DAffine2,
|
transform: DAffine2,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NodeGraphUpdate {
|
pub enum NodeGraphUpdate {
|
||||||
ExecutionResponse(ExecutionResponse),
|
ExecutionResponse(ExecutionResponse),
|
||||||
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
|
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
|
||||||
}
|
}
|
||||||
@@ -119,7 +122,7 @@ thread_local! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl NodeRuntime {
|
impl NodeRuntime {
|
||||||
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
|
pub fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
executor: DynamicExecutor::default(),
|
executor: DynamicExecutor::default(),
|
||||||
receiver,
|
receiver,
|
||||||
@@ -136,6 +139,7 @@ impl NodeRuntime {
|
|||||||
|
|
||||||
thumbnail_renders: Default::default(),
|
thumbnail_renders: Default::default(),
|
||||||
click_targets: HashMap::new(),
|
click_targets: HashMap::new(),
|
||||||
|
vector_modify: HashMap::new(),
|
||||||
upstream_transforms: HashMap::new(),
|
upstream_transforms: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +170,7 @@ impl NodeRuntime {
|
|||||||
result,
|
result,
|
||||||
responses,
|
responses,
|
||||||
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
|
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
|
||||||
|
new_vector_modify: self.vector_modify.clone(),
|
||||||
new_upstream_transforms: self.upstream_transforms.clone(),
|
new_upstream_transforms: self.upstream_transforms.clone(),
|
||||||
resolved_types: self.resolved_types.clone(),
|
resolved_types: self.resolved_types.clone(),
|
||||||
node_graph_errors: core::mem::take(&mut self.node_graph_errors),
|
node_graph_errors: core::mem::take(&mut self.node_graph_errors),
|
||||||
@@ -233,7 +238,7 @@ impl NodeRuntime {
|
|||||||
Some(t) if t == concrete!(WasmEditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()),
|
Some(t) if t == concrete!(WasmEditorApi) => (&self.executor).execute(editor_api).await.map_err(|e| e.to_string()),
|
||||||
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
|
Some(t) if t == concrete!(()) => (&self.executor).execute(()).await.map_err(|e| e.to_string()),
|
||||||
Some(t) => Err(format!("Invalid input type {t:?}")),
|
Some(t) => Err(format!("Invalid input type {t:?}")),
|
||||||
_ => Err("No input type".to_string()),
|
_ => Err(format!("No input type:\n{:?}", self.node_graph_errors)),
|
||||||
};
|
};
|
||||||
let result = match result {
|
let result = match result {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
@@ -326,6 +331,9 @@ impl NodeRuntime {
|
|||||||
});
|
});
|
||||||
*old_thumbnail_svg = new_thumbnail_svg;
|
*old_thumbnail_svg = new_thumbnail_svg;
|
||||||
}
|
}
|
||||||
|
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Footprint, VectorData>>() {
|
||||||
|
// Insert the vector modify if we are dealing with vector data
|
||||||
|
self.vector_modify.insert(parent_network_node_id, record.output.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is `VectorData`, `ImageFrame`, or `GraphicElement` data:
|
// If this is `VectorData`, `ImageFrame`, or `GraphicElement` data:
|
||||||
@@ -378,6 +386,13 @@ pub async fn run_node_graph() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn replace_node_runtime(runtime: NodeRuntime) -> Option<NodeRuntime> {
|
||||||
|
NODE_RUNTIME.with(|node_runtime| {
|
||||||
|
let mut node_runtime = node_runtime.borrow_mut();
|
||||||
|
node_runtime.replace(runtime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NodeGraphExecutor {
|
pub struct NodeGraphExecutor {
|
||||||
sender: Sender<NodeRuntimeMessage>,
|
sender: Sender<NodeRuntimeMessage>,
|
||||||
@@ -394,9 +409,7 @@ impl Default for NodeGraphExecutor {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let (request_sender, request_receiver) = std::sync::mpsc::channel();
|
let (request_sender, request_receiver) = std::sync::mpsc::channel();
|
||||||
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
let (response_sender, response_receiver) = std::sync::mpsc::channel();
|
||||||
NODE_RUNTIME.with(|runtime| {
|
replace_node_runtime(NodeRuntime::new(request_receiver, response_sender));
|
||||||
runtime.borrow_mut().replace(NodeRuntime::new(request_receiver, response_sender));
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
futures: Default::default(),
|
futures: Default::default(),
|
||||||
@@ -556,6 +569,7 @@ impl NodeGraphExecutor {
|
|||||||
result,
|
result,
|
||||||
responses: existing_responses,
|
responses: existing_responses,
|
||||||
new_click_targets,
|
new_click_targets,
|
||||||
|
new_vector_modify,
|
||||||
new_upstream_transforms,
|
new_upstream_transforms,
|
||||||
resolved_types,
|
resolved_types,
|
||||||
node_graph_errors,
|
node_graph_errors,
|
||||||
@@ -567,15 +581,18 @@ impl NodeGraphExecutor {
|
|||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
let Ok(node_graph_output) = result else {
|
let node_graph_output = match result {
|
||||||
// Clear the click targets while the graph is in an un-renderable state
|
Ok(output) => output,
|
||||||
document.metadata.update_click_targets(HashMap::new());
|
Err(e) => {
|
||||||
|
// Clear the click targets while the graph is in an un-renderable state
|
||||||
|
document.metadata.update_from_monitor(HashMap::new(), HashMap::new());
|
||||||
|
|
||||||
return Err("Node graph evaluation failed".to_string());
|
return Err(format!("Node graph evaluation failed:\n{e}"));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.metadata.update_transforms(new_upstream_transforms);
|
document.metadata.update_transforms(new_upstream_transforms);
|
||||||
document.metadata.update_click_targets(new_click_targets);
|
document.metadata.update_from_monitor(new_click_targets, new_vector_modify);
|
||||||
|
|
||||||
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||||
if let Some(export_config) = execution_context.export_config {
|
if let Some(export_config) = execution_context.export_config {
|
||||||
|
|||||||
@@ -47,9 +47,9 @@
|
|||||||
// Gradient color stops
|
// Gradient color stops
|
||||||
$: gradient = colorOrGradient instanceof Gradient ? colorOrGradient : undefined;
|
$: gradient = colorOrGradient instanceof Gradient ? colorOrGradient : undefined;
|
||||||
let activeIndex = 0 as number | undefined;
|
let activeIndex = 0 as number | undefined;
|
||||||
$: selectedGradientColour = (activeIndex !== undefined && gradient?.atIndex(activeIndex)?.color) || (Color.fromCSS("black") as Color);
|
$: selectedGradientColor = (activeIndex !== undefined && gradient?.atIndex(activeIndex)?.color) || (Color.fromCSS("black") as Color);
|
||||||
// Currently viewed color
|
// Currently viewed color
|
||||||
$: color = colorOrGradient instanceof Color ? colorOrGradient : selectedGradientColour;
|
$: color = colorOrGradient instanceof Color ? colorOrGradient : selectedGradientColor;
|
||||||
// New color components
|
// New color components
|
||||||
let hue = hsva.h;
|
let hue = hsva.h;
|
||||||
let saturation = hsva.s;
|
let saturation = hsva.s;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={`checkbox-input-${id}`}
|
id={`checkbox-input-${id}`}
|
||||||
{checked}
|
bind:checked
|
||||||
on:change={(_) => dispatch("checked", inputElement?.checked || false)}
|
on:change={(_) => dispatch("checked", inputElement?.checked || false)}
|
||||||
{disabled}
|
{disabled}
|
||||||
tabindex={disabled ? -1 : 0}
|
tabindex={disabled ? -1 : 0}
|
||||||
|
|||||||
@@ -331,7 +331,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no buttons are down, we are stuck in the drag state after having released the mouse, so we should exit.
|
// If no buttons are down, we are stuck in the drag state after having released the mouse, so we should exit.
|
||||||
if (e.buttons === 0) {
|
// For some reason on firefox in wayland the button is -1 and the buttons is 0.
|
||||||
|
if (e.buttons === 0 && e.button !== -1) {
|
||||||
document.exitPointerLock();
|
document.exitPointerLock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
TriggerImport,
|
TriggerImport,
|
||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
TriggerRevokeBlobUrl,
|
TriggerRevokeBlobUrl,
|
||||||
|
TriggerUpgradeDocumentToVectorManipulationFormat,
|
||||||
UpdateActiveDocument,
|
UpdateActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
} from "@graphite/wasm-communication/messages";
|
} from "@graphite/wasm-communication/messages";
|
||||||
@@ -110,6 +111,11 @@ export function createPortfolioState(editor: Editor) {
|
|||||||
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
|
editor.subscriptions.subscribeJsMessage(TriggerRevokeBlobUrl, async (triggerRevokeBlobUrl) => {
|
||||||
URL.revokeObjectURL(triggerRevokeBlobUrl.url);
|
URL.revokeObjectURL(triggerRevokeBlobUrl.url);
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(TriggerUpgradeDocumentToVectorManipulationFormat, async (triggerUpgradeDocumentToVectorManipulationFormat) => {
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
const { documentId, documentName, documentIsAutoSaved, documentIsSaved, documentSerializedContent } = triggerUpgradeDocumentToVectorManipulationFormat;
|
||||||
|
editor.handle.triggerUpgradeDocumentToVectorManipulationFormat(documentId, documentName, documentIsAutoSaved, documentIsSaved, documentSerializedContent);
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|||||||
@@ -788,6 +788,15 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
|
|||||||
|
|
||||||
export class TriggerViewportResize extends JsMessage {}
|
export class TriggerViewportResize extends JsMessage {}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
export class TriggerUpgradeDocumentToVectorManipulationFormat extends JsMessage {
|
||||||
|
readonly documentId!: bigint;
|
||||||
|
readonly documentName!: string;
|
||||||
|
readonly documentIsAutoSaved!: boolean;
|
||||||
|
readonly documentIsSaved!: boolean;
|
||||||
|
readonly documentSerializedContent!: string;
|
||||||
|
}
|
||||||
|
|
||||||
// WIDGET PROPS
|
// WIDGET PROPS
|
||||||
|
|
||||||
export abstract class WidgetProps {
|
export abstract class WidgetProps {
|
||||||
@@ -1439,6 +1448,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
TriggerSavePreferences,
|
TriggerSavePreferences,
|
||||||
TriggerTextCommit,
|
TriggerTextCommit,
|
||||||
TriggerTextCopy,
|
TriggerTextCopy,
|
||||||
|
TriggerUpgradeDocumentToVectorManipulationFormat,
|
||||||
TriggerViewportResize,
|
TriggerViewportResize,
|
||||||
TriggerVisitLink,
|
TriggerVisitLink,
|
||||||
UpdateActiveDocument,
|
UpdateActiveDocument,
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ js-sys = { workspace = true }
|
|||||||
wasm-bindgen-futures = { workspace = true }
|
wasm-bindgen-futures = { workspace = true }
|
||||||
ron = { workspace = true, optional = true }
|
ron = { workspace = true, optional = true }
|
||||||
bezier-rs = { workspace = true }
|
bezier-rs = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
# We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm
|
# We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm
|
||||||
wgpu = { workspace = true, features = ["fragile-send-sync-non-atomic-wasm"] }
|
wgpu = { workspace = true, features = ["fragile-send-sync-non-atomic-wasm"] }
|
||||||
meval = "0.2.0"
|
meval = "0.2.0"
|
||||||
|
|||||||
@@ -636,6 +636,166 @@ impl EditorHandle {
|
|||||||
pub fn inject_imaginate_poll_server_status(&self) {
|
pub fn inject_imaginate_poll_server_status(&self) {
|
||||||
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
self.dispatch(PortfolioMessage::ImaginatePollServerStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
#[wasm_bindgen(js_name = triggerUpgradeDocumentToVectorManipulationFormat)]
|
||||||
|
pub async fn upgrade_document_to_vector_manipulation_format(
|
||||||
|
&self,
|
||||||
|
document_id: u64,
|
||||||
|
document_name: String,
|
||||||
|
document_is_auto_saved: bool,
|
||||||
|
document_is_saved: bool,
|
||||||
|
document_serialized_content: String,
|
||||||
|
) {
|
||||||
|
use editor::messages::portfolio::document::graph_operation::transform_utils::*;
|
||||||
|
use editor::messages::portfolio::document::graph_operation::utility_types::*;
|
||||||
|
use editor::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
|
||||||
|
use editor::node_graph_executor::replace_node_runtime;
|
||||||
|
use editor::node_graph_executor::NodeRuntime;
|
||||||
|
use graph_craft::document::NodeInput;
|
||||||
|
use graph_craft::document::{value::TaggedValue, DocumentNodeImplementation};
|
||||||
|
use graphene_core::vector::*;
|
||||||
|
|
||||||
|
let (_, request_receiver) = std::sync::mpsc::channel();
|
||||||
|
let (response_sender, _) = std::sync::mpsc::channel();
|
||||||
|
let old_runtime = replace_node_runtime(NodeRuntime::new(request_receiver, response_sender));
|
||||||
|
|
||||||
|
let document_serialized_content = document_serialized_content.replace("\"ManipulatorGroupIds\"", "\"PointIds\"");
|
||||||
|
|
||||||
|
let mut editor = Editor::new();
|
||||||
|
let document_id = DocumentId(document_id);
|
||||||
|
editor.handle_message(PortfolioMessage::OpenDocumentFileWithId {
|
||||||
|
document_id,
|
||||||
|
document_name: document_name.clone(),
|
||||||
|
document_is_auto_saved,
|
||||||
|
document_is_saved,
|
||||||
|
document_serialized_content: document_serialized_content.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||||
|
for node in document.network.nodes.values_mut().filter(|d| d.name == "Artboard") {
|
||||||
|
if let Some(network) = node.implementation.get_network_mut() {
|
||||||
|
for node in network.nodes.values_mut() {
|
||||||
|
if node.name == "To Artboard" {
|
||||||
|
node.implementation = DocumentNodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _, _, _, _>");
|
||||||
|
if node.inputs.len() != 6 {
|
||||||
|
node.inputs.insert(2, NodeInput::value(TaggedValue::IVec2(glam::IVec2::default()), false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler;
|
||||||
|
portfolio
|
||||||
|
.executor
|
||||||
|
.submit_node_graph_evaluation(portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(), glam::UVec2::ONE)
|
||||||
|
.unwrap();
|
||||||
|
editor::node_graph_executor::run_node_graph().await;
|
||||||
|
|
||||||
|
let mut messages = VecDeque::new();
|
||||||
|
if let Err(err) = editor.poll_node_graph_evaluation(&mut messages) {
|
||||||
|
log::warn!(
|
||||||
|
"While attempting to upgrade the old document format, the graph evaluation failed which is necessary for the upgrade process:\n{:#?}",
|
||||||
|
err
|
||||||
|
);
|
||||||
|
|
||||||
|
replace_node_runtime(old_runtime.unwrap());
|
||||||
|
|
||||||
|
let document_name = document_name.clone() + "__DO_NOT_UPGRADE__";
|
||||||
|
self.dispatch(PortfolioMessage::OpenDocumentFileWithId {
|
||||||
|
document_id,
|
||||||
|
document_name,
|
||||||
|
document_is_auto_saved,
|
||||||
|
document_is_saved,
|
||||||
|
document_serialized_content,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut updated_nodes = HashSet::new();
|
||||||
|
let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap();
|
||||||
|
document.metadata.load_structure(&document.network);
|
||||||
|
for node in document.network.nodes.iter().filter(|(_, d)| d.name == "Merge").map(|(id, _)| *id).collect::<Vec<_>>() {
|
||||||
|
let layer = LayerNodeIdentifier::new(node, &document.network);
|
||||||
|
if document.metadata.is_folder(layer) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bounds = LayerBounds::new(&document.metadata, layer);
|
||||||
|
|
||||||
|
let mut responses = VecDeque::new();
|
||||||
|
let mut shape = None;
|
||||||
|
|
||||||
|
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), &mut document.network, &mut document.metadata, &mut document.node_graph_handler, &mut responses) {
|
||||||
|
modify_inputs.modify_existing_inputs("Transform", |inputs, node_id, metadata| {
|
||||||
|
if !updated_nodes.insert(node_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform = get_current_transform(&inputs);
|
||||||
|
let upstream_transform = metadata.upstream_transform(node_id);
|
||||||
|
let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(&inputs))));
|
||||||
|
|
||||||
|
update_transform(inputs, pivot_transform * transform * pivot_transform.inverse());
|
||||||
|
});
|
||||||
|
modify_inputs.modify_existing_inputs("Shape", |inputs, node_id, _metadata| {
|
||||||
|
if !updated_nodes.insert(node_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let empty_vec = Vec::new();
|
||||||
|
let path_data = if let NodeInput::Value {
|
||||||
|
tagged_value: TaggedValue::Subpaths(translation),
|
||||||
|
..
|
||||||
|
} = &inputs[0]
|
||||||
|
{
|
||||||
|
translation
|
||||||
|
} else {
|
||||||
|
&empty_vec
|
||||||
|
};
|
||||||
|
|
||||||
|
let empty_vec = Vec::new();
|
||||||
|
let colinear_manipulators = if let NodeInput::Value {
|
||||||
|
tagged_value: TaggedValue::PointIds(translation),
|
||||||
|
..
|
||||||
|
} = &inputs[1]
|
||||||
|
{
|
||||||
|
translation
|
||||||
|
} else {
|
||||||
|
&empty_vec
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut vector_data = VectorData::from_subpaths(path_data, false);
|
||||||
|
vector_data.colinear_manipulators = colinear_manipulators
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
shape = Some((node_id, VectorModification::create_from_vector(&vector_data)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some((id, modification)) = shape {
|
||||||
|
let metadata = document.network.nodes.remove(&id).map(|node| node.metadata).unwrap_or_default();
|
||||||
|
let node_type = resolve_document_node_type("Path").unwrap();
|
||||||
|
|
||||||
|
let document_node = node_type.to_document_node_default_inputs([None, Some(NodeInput::value(TaggedValue::VectorModification(modification), false))], metadata);
|
||||||
|
document.network.nodes.insert(id, document_node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let document_serialized_content = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap().serialize_document();
|
||||||
|
|
||||||
|
replace_node_runtime(old_runtime.unwrap());
|
||||||
|
|
||||||
|
self.dispatch(PortfolioMessage::OpenDocumentFileWithId {
|
||||||
|
document_id,
|
||||||
|
document_name,
|
||||||
|
document_is_auto_saved,
|
||||||
|
document_is_saved,
|
||||||
|
document_serialized_content,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -765,7 +925,12 @@ async fn poll_node_graph_evaluation() {
|
|||||||
|
|
||||||
editor_and_handle(|editor, handle| {
|
editor_and_handle(|editor, handle| {
|
||||||
let mut messages = VecDeque::new();
|
let mut messages = VecDeque::new();
|
||||||
editor.poll_node_graph_evaluation(&mut messages);
|
if let Err(e) = editor.poll_node_graph_evaluation(&mut messages) {
|
||||||
|
// TODO: This is a hacky way to suppress the error, but it shouldn't be generated in the first place
|
||||||
|
if e != "No active document" {
|
||||||
|
error!("Error evaluating node graph:\n{e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Send each `FrontendMessage` to the JavaScript frontend
|
// Send each `FrontendMessage` to the JavaScript frontend
|
||||||
for response in messages.into_iter().flat_map(|message| editor.handle_message(message)) {
|
for response in messages.into_iter().flat_map(|message| editor.handle_message(message)) {
|
||||||
|
|||||||
@@ -356,5 +356,8 @@ mod tests {
|
|||||||
|
|
||||||
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
let bezier2 = Bezier::from_quadratic_coordinates(0., 0., 0., 100., 100., 100.);
|
||||||
assert_eq!(bezier2.project(DVec2::new(100., 0.)), 0.);
|
assert_eq!(bezier2.project(DVec2::new(100., 0.)), 0.);
|
||||||
|
|
||||||
|
let bezier3 = Bezier::from_cubic_coordinates(-50.0, -50.0, -50.0, -50.0, 50.0, -50.0, 50.0, -50.0);
|
||||||
|
assert_eq!(DVec2::new(0., -50.), bezier3.evaluate(TValue::Parametric(bezier3.project(DVec2::new(0., -50.)))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ impl BezierHandles {
|
|||||||
matches!(self, Self::Cubic { .. })
|
matches!(self, Self::Cubic { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_finite(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
BezierHandles::Linear => true,
|
||||||
|
BezierHandles::Quadratic { handle } => handle.is_finite(),
|
||||||
|
BezierHandles::Cubic { handle_start, handle_end } => handle_start.is_finite() && handle_end.is_finite(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
/// Get the coordinates of the bezier segment's first handle point. This represents the only handle in a quadratic segment.
|
||||||
pub fn start(&self) -> Option<DVec2> {
|
pub fn start(&self) -> Option<DVec2> {
|
||||||
match *self {
|
match *self {
|
||||||
@@ -64,6 +72,18 @@ impl BezierHandles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_start(&mut self, delta: DVec2) {
|
||||||
|
if let BezierHandles::Cubic { handle_start, .. } | BezierHandles::Quadratic { handle: handle_start } = self {
|
||||||
|
*handle_start += delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_end(&mut self, delta: DVec2) {
|
||||||
|
if let BezierHandles::Cubic { handle_end, .. } = self {
|
||||||
|
*handle_end += delta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.
|
/// Returns a Bezier curve that results from applying the transformation function to each handle point in the Bezier.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {
|
pub fn apply_transformation(&self, transformation_function: impl Fn(DVec2) -> DVec2) -> Self {
|
||||||
@@ -80,6 +100,17 @@ impl BezierHandles {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn flipped(self) -> Self {
|
||||||
|
match self {
|
||||||
|
BezierHandles::Cubic { handle_start, handle_end } => Self::Cubic {
|
||||||
|
handle_start: handle_end,
|
||||||
|
handle_end: handle_start,
|
||||||
|
},
|
||||||
|
_ => self,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dyn-any")]
|
#[cfg(feature = "dyn-any")]
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ impl Bezier {
|
|||||||
/// - `distance` - The offset's distance from the curve. Positive values will offset the curve in the same direction as the endpoint normals,
|
/// - `distance` - The offset's distance from the curve. Positive values will offset the curve in the same direction as the endpoint normals,
|
||||||
/// while negative values will offset in the opposite direction.
|
/// while negative values will offset in the opposite direction.
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#bezier/offset/solo" title="Offset Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#bezier/offset/solo" title="Offset Demo"></iframe>
|
||||||
pub fn offset<ManipulatorGroupId: crate::Identifier>(&self, distance: f64) -> Subpath<ManipulatorGroupId> {
|
pub fn offset<PointId: crate::Identifier>(&self, distance: f64) -> Subpath<PointId> {
|
||||||
if self.is_point() {
|
if self.is_point() {
|
||||||
return Subpath::from_bezier(self);
|
return Subpath::from_bezier(self);
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,7 @@ impl Bezier {
|
|||||||
/// Version of the `offset` function which scales the offset such that the start of the offset is `start_distance` from the original curve, while the end of
|
/// Version of the `offset` function which scales the offset such that the start of the offset is `start_distance` from the original curve, while the end of
|
||||||
/// of the offset is `end_distance` from the original curve. The curve transitions from `start_distance` to `end_distance` gradually, proportional to the
|
/// of the offset is `end_distance` from the original curve. The curve transitions from `start_distance` to `end_distance` gradually, proportional to the
|
||||||
/// distance along the equation (`t`-value) of the curve. Similarly to the `offset` function, the returned result is an approximation.
|
/// distance along the equation (`t`-value) of the curve. Similarly to the `offset` function, the returned result is an approximation.
|
||||||
pub fn graduated_offset<ManipulatorGroupId: crate::Identifier>(&self, start_distance: f64, end_distance: f64) -> Subpath<ManipulatorGroupId> {
|
pub fn graduated_offset<PointId: crate::Identifier>(&self, start_distance: f64, end_distance: f64) -> Subpath<PointId> {
|
||||||
let reduced = self.reduce(None);
|
let reduced = self.reduce(None);
|
||||||
let mut next_start_distance = start_distance;
|
let mut next_start_distance = start_distance;
|
||||||
let distance_difference = end_distance - start_distance;
|
let distance_difference = end_distance - start_distance;
|
||||||
@@ -414,7 +414,7 @@ impl Bezier {
|
|||||||
/// Outline takes the following parameter:
|
/// Outline takes the following parameter:
|
||||||
/// - `distance` - The outline's distance from the curve.
|
/// - `distance` - The outline's distance from the curve.
|
||||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#bezier/outline/solo" title="Outline Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#bezier/outline/solo" title="Outline Demo"></iframe>
|
||||||
pub fn outline<ManipulatorGroupId: crate::Identifier>(&self, distance: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
pub fn outline<PointId: crate::Identifier>(&self, distance: f64, cap: Cap) -> Subpath<PointId> {
|
||||||
let (pos_offset, neg_offset) = if self.is_point() {
|
let (pos_offset, neg_offset) = if self.is_point() {
|
||||||
(
|
(
|
||||||
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance)], false),
|
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance)], false),
|
||||||
@@ -434,13 +434,13 @@ impl Bezier {
|
|||||||
/// Version of the `outline` function which draws the outline at the specified distances away from the curve.
|
/// Version of the `outline` function which draws the outline at the specified distances away from the curve.
|
||||||
/// The outline begins `start_distance` away, and gradually move to being `end_distance` away.
|
/// The outline begins `start_distance` away, and gradually move to being `end_distance` away.
|
||||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#bezier/graduated-outline/solo" title="Graduated Outline Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#bezier/graduated-outline/solo" title="Graduated Outline Demo"></iframe>
|
||||||
pub fn graduated_outline<ManipulatorGroupId: crate::Identifier>(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
pub fn graduated_outline<PointId: crate::Identifier>(&self, start_distance: f64, end_distance: f64, cap: Cap) -> Subpath<PointId> {
|
||||||
self.skewed_outline(start_distance, end_distance, end_distance, start_distance, cap)
|
self.skewed_outline(start_distance, end_distance, end_distance, start_distance, cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Version of the `graduated_outline` function that allows for the 4 corners of the outline to be different distances away from the curve.
|
/// Version of the `graduated_outline` function that allows for the 4 corners of the outline to be different distances away from the curve.
|
||||||
/// <iframe frameBorder="0" width="100%" height="475px" src="https://graphite.rs/libraries/bezier-rs#bezier/skewed-outline/solo" title="Skewed Outline Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="475px" src="https://graphite.rs/libraries/bezier-rs#bezier/skewed-outline/solo" title="Skewed Outline Demo"></iframe>
|
||||||
pub fn skewed_outline<ManipulatorGroupId: crate::Identifier>(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
pub fn skewed_outline<PointId: crate::Identifier>(&self, distance1: f64, distance2: f64, distance3: f64, distance4: f64, cap: Cap) -> Subpath<PointId> {
|
||||||
let (pos_offset, neg_offset) = if self.is_point() {
|
let (pos_offset, neg_offset) = if self.is_point() {
|
||||||
(
|
(
|
||||||
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance1)], false),
|
Subpath::new(vec![ManipulatorGroup::new_anchor(self.start() + DVec2::NEG_Y * distance1)], false),
|
||||||
@@ -776,7 +776,7 @@ mod tests {
|
|||||||
.all(|(curve, t_pair)| curve.abs_diff_eq(&bezier.trim(TValue::Parametric(t_pair[0]), TValue::Parametric(t_pair[1])), MAX_ABSOLUTE_DIFFERENCE)))
|
.all(|(curve, t_pair)| curve.abs_diff_eq(&bezier.trim(TValue::Parametric(t_pair[0]), TValue::Parametric(t_pair[1])), MAX_ABSOLUTE_DIFFERENCE)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_valid_offset<ManipulatorGroupId: crate::Identifier>(bezier: &Bezier, offset: &Subpath<ManipulatorGroupId>, expected_distance: f64) {
|
fn assert_valid_offset<PointId: crate::Identifier>(bezier: &Bezier, offset: &Subpath<PointId>, expected_distance: f64) {
|
||||||
// Verify that the offset is smooth
|
// Verify that the offset is smooth
|
||||||
if offset.len() > 1 {
|
if offset.len() > 1 {
|
||||||
offset.iter().take(offset.len() - 2).zip(offset.iter().skip(1)).for_each(|beziers_pair| {
|
offset.iter().take(offset.len() - 2).zip(offset.iter().skip(1)).for_each(|beziers_pair| {
|
||||||
|
|||||||
@@ -37,6 +37,6 @@ pub fn compare_arcs(arc1: CircleArc, arc2: CircleArc) -> bool {
|
|||||||
/// Compare Subpath by verifying that their bezier segments match.
|
/// Compare Subpath by verifying that their bezier segments match.
|
||||||
/// In this way, matching quadratic segments where the handles are on opposite manipulator groups will be considered equal.
|
/// In this way, matching quadratic segments where the handles are on opposite manipulator groups will be considered equal.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn compare_subpaths<ManipulatorGroupId: crate::Identifier>(subpath1: &Subpath<ManipulatorGroupId>, subpath2: &Subpath<ManipulatorGroupId>) -> bool {
|
pub fn compare_subpaths<PointId: crate::Identifier>(subpath1: &Subpath<PointId>, subpath2: &Subpath<PointId>) -> bool {
|
||||||
subpath1.len() == subpath2.len() && subpath1.closed() == subpath2.closed() && subpath1.iter().eq(subpath2.iter())
|
subpath1.len() == subpath2.len() && subpath1.closed() == subpath2.closed() && subpath1.iter().eq(subpath2.iter())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![allow(dead_code, unused_imports, unused_import_braces)]
|
||||||
|
|
||||||
pub(crate) mod compare;
|
pub(crate) mod compare;
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ use glam::DVec2;
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
|
/// Functionality relating to core `Subpath` operations, such as constructors and `iter`.
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
|
/// Create a new `Subpath` using a list of [ManipulatorGroup]s.
|
||||||
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
|
/// A `Subpath` with less than 2 [ManipulatorGroup]s may not be closed.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>, closed: bool) -> Self {
|
pub fn new(manipulator_groups: Vec<ManipulatorGroup<PointId>>, closed: bool) -> Self {
|
||||||
assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
|
assert!(!closed || manipulator_groups.len() > 1, "A closed Subpath must contain more than 1 ManipulatorGroup.");
|
||||||
Self { manipulator_groups, closed }
|
Self { manipulator_groups, closed }
|
||||||
}
|
}
|
||||||
@@ -22,13 +22,13 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: bezier.start(),
|
anchor: bezier.start(),
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: bezier.handle_start(),
|
out_handle: bezier.handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
},
|
},
|
||||||
ManipulatorGroup {
|
ManipulatorGroup {
|
||||||
anchor: bezier.end(),
|
anchor: bezier.end(),
|
||||||
in_handle: bezier.handle_end(),
|
in_handle: bezier.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
false,
|
false,
|
||||||
@@ -48,17 +48,17 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: first.start(),
|
anchor: first.start(),
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: first.handle_start(),
|
out_handle: first.handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
}];
|
}];
|
||||||
let mut inner_groups: Vec<ManipulatorGroup<ManipulatorGroupId>> = beziers
|
let mut inner_groups: Vec<ManipulatorGroup<PointId>> = beziers
|
||||||
.windows(2)
|
.windows(2)
|
||||||
.map(|bezier_pair| ManipulatorGroup {
|
.map(|bezier_pair| ManipulatorGroup {
|
||||||
anchor: bezier_pair[1].start(),
|
anchor: bezier_pair[1].start(),
|
||||||
in_handle: bezier_pair[0].handle_end(),
|
in_handle: bezier_pair[0].handle_end(),
|
||||||
out_handle: bezier_pair[1].handle_start(),
|
out_handle: bezier_pair[1].handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>();
|
.collect::<Vec<ManipulatorGroup<PointId>>>();
|
||||||
manipulator_groups.append(&mut inner_groups);
|
manipulator_groups.append(&mut inner_groups);
|
||||||
|
|
||||||
let last = beziers.last().unwrap();
|
let last = beziers.last().unwrap();
|
||||||
@@ -67,7 +67,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: last.end(),
|
anchor: last.end(),
|
||||||
in_handle: last.handle_end(),
|
in_handle: last.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
});
|
});
|
||||||
return Subpath::new(manipulator_groups, false);
|
return Subpath::new(manipulator_groups, false);
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
/// Returns an iterator of the [Bezier]s along the `Subpath`.
|
||||||
pub fn iter(&self) -> SubpathIter<ManipulatorGroupId> {
|
pub fn iter(&self) -> SubpathIter<PointId> {
|
||||||
SubpathIter {
|
SubpathIter {
|
||||||
subpath: self,
|
subpath: self,
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -113,7 +113,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.
|
/// Returns an iterator of the [Bezier]s along the `Subpath` always considering it as a closed subpath.
|
||||||
pub fn iter_closed(&self) -> SubpathIter<ManipulatorGroupId> {
|
pub fn iter_closed(&self) -> SubpathIter<PointId> {
|
||||||
SubpathIter {
|
SubpathIter {
|
||||||
subpath: self,
|
subpath: self,
|
||||||
index: 0,
|
index: 0,
|
||||||
@@ -122,12 +122,12 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.
|
/// Returns a slice of the [ManipulatorGroup]s in the `Subpath`.
|
||||||
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<ManipulatorGroupId>] {
|
pub fn manipulator_groups(&self) -> &[ManipulatorGroup<PointId>] {
|
||||||
&self.manipulator_groups
|
&self.manipulator_groups
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.
|
/// Returns a mutable reference to the [ManipulatorGroup]s in the `Subpath`.
|
||||||
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
pub fn manipulator_groups_mut(&mut self) -> &mut Vec<ManipulatorGroup<PointId>> {
|
||||||
&mut self.manipulator_groups
|
&mut self.manipulator_groups
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,9 +232,13 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
|
|
||||||
/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.
|
/// Constructs a rounded rectangle with `corner1` and `corner2` as the two corners and `corner_radii` as the radii of the corners: `[top_left, top_right, bottom_right, bottom_left]`.
|
||||||
pub fn new_rounded_rect(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {
|
pub fn new_rounded_rect(corner1: DVec2, corner2: DVec2, corner_radii: [f64; 4]) -> Self {
|
||||||
|
if corner_radii.iter().all(|radii| radii.abs() < f64::EPSILON * 100.) {
|
||||||
|
return Self::new_rect(corner1, corner2);
|
||||||
|
}
|
||||||
|
|
||||||
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
use std::f64::consts::{FRAC_1_SQRT_2, PI};
|
||||||
|
|
||||||
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
let new_arc = |center: DVec2, corner: DVec2, radius: f64| -> Vec<ManipulatorGroup<PointId>> {
|
||||||
let point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
let point1 = center + DVec2::from_angle(-PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||||
let point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
let point2 = center + DVec2::from_angle(PI * 0.25).rotate(corner - center) * FRAC_1_SQRT_2;
|
||||||
if radius == 0. {
|
if radius == 0. {
|
||||||
@@ -245,10 +249,8 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014;
|
||||||
let handle_offset = radius * HANDLE_OFFSET_FACTOR;
|
let handle_offset = radius * HANDLE_OFFSET_FACTOR;
|
||||||
vec![
|
vec![
|
||||||
ManipulatorGroup::new_anchor(point1),
|
|
||||||
ManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),
|
ManipulatorGroup::new(point1, None, Some(point1 + handle_offset * (corner - point1).normalize())),
|
||||||
ManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),
|
ManipulatorGroup::new(point2, Some(point2 + handle_offset * (corner - point2).normalize()), None),
|
||||||
ManipulatorGroup::new_anchor(point2),
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
Self::new(
|
Self::new(
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::utils::{SubpathTValue, TValue, TValueType};
|
|||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
|
|
||||||
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
/// Functionality relating to looking up properties of the `Subpath` or points along the `Subpath`.
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
/// Return a selection of equidistant points on the bezier curve.
|
/// Return a selection of equidistant points on the bezier curve.
|
||||||
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
/// If no value is provided for `steps`, then the function will default `steps` to be 10.
|
||||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/lookup-table/solo" title="Lookup-Table Demo"></iframe>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::consts::MAX_ABSOLUTE_DIFFERENCE;
|
|||||||
use crate::utils::f64_compare;
|
use crate::utils::f64_compare;
|
||||||
use crate::{SubpathTValue, TValue};
|
use crate::{SubpathTValue, TValue};
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
/// Get whether the subpath is closed.
|
/// Get whether the subpath is closed.
|
||||||
pub fn closed(&self) -> bool {
|
pub fn closed(&self) -> bool {
|
||||||
self.closed
|
self.closed
|
||||||
@@ -14,40 +14,40 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
self.closed = new_closed;
|
self.closed = new_closed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access a [ManipulatorGroup] from a ManipulatorGroupId.
|
/// Access a [ManipulatorGroup] from a PointId.
|
||||||
pub fn manipulator_from_id(&self, id: ManipulatorGroupId) -> Option<&ManipulatorGroup<ManipulatorGroupId>> {
|
pub fn manipulator_from_id(&self, id: PointId) -> Option<&ManipulatorGroup<PointId>> {
|
||||||
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
self.manipulator_groups.iter().find(|manipulator_group| manipulator_group.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access a mutable [ManipulatorGroup] from a ManipulatorGroupId.
|
/// Access a mutable [ManipulatorGroup] from a PointId.
|
||||||
pub fn manipulator_mut_from_id(&mut self, id: ManipulatorGroupId) -> Option<&mut ManipulatorGroup<ManipulatorGroupId>> {
|
pub fn manipulator_mut_from_id(&mut self, id: PointId) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||||
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
self.manipulator_groups.iter_mut().find(|manipulator_group| manipulator_group.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the index of a [ManipulatorGroup] from a ManipulatorGroupId.
|
/// Access the index of a [ManipulatorGroup] from a PointId.
|
||||||
pub fn manipulator_index_from_id(&self, id: ManipulatorGroupId) -> Option<usize> {
|
pub fn manipulator_index_from_id(&self, id: PointId) -> Option<usize> {
|
||||||
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
self.manipulator_groups.iter().position(|manipulator_group| manipulator_group.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a manipulator group at an index.
|
/// Insert a manipulator group at an index.
|
||||||
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<ManipulatorGroupId>) {
|
pub fn insert_manipulator_group(&mut self, index: usize, group: ManipulatorGroup<PointId>) {
|
||||||
assert!(group.is_finite(), "Inserting non finite manipulator group");
|
assert!(group.is_finite(), "Inserting non finite manipulator group");
|
||||||
self.manipulator_groups.insert(index, group)
|
self.manipulator_groups.insert(index, group)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a manipulator group to the end.
|
/// Push a manipulator group to the end.
|
||||||
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<ManipulatorGroupId>) {
|
pub fn push_manipulator_group(&mut self, group: ManipulatorGroup<PointId>) {
|
||||||
assert!(group.is_finite(), "Pushing non finite manipulator group");
|
assert!(group.is_finite(), "Pushing non finite manipulator group");
|
||||||
self.manipulator_groups.push(group)
|
self.manipulator_groups.push(group)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the last manipulator
|
/// Get a mutable reference to the last manipulator
|
||||||
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<ManipulatorGroupId>> {
|
pub fn last_manipulator_group_mut(&mut self) -> Option<&mut ManipulatorGroup<PointId>> {
|
||||||
self.manipulator_groups.last_mut()
|
self.manipulator_groups.last_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a manipulator group at an index.
|
/// Remove a manipulator group at an index.
|
||||||
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<ManipulatorGroupId> {
|
pub fn remove_manipulator_group(&mut self, index: usize) -> ManipulatorGroup<PointId> {
|
||||||
self.manipulator_groups.remove(index)
|
self.manipulator_groups.remove(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: first.end(),
|
anchor: first.end(),
|
||||||
in_handle: first.handle_end(),
|
in_handle: first.handle_end(),
|
||||||
out_handle: second.handle_start(),
|
out_handle: second.handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
};
|
};
|
||||||
let number_of_groups = self.manipulator_groups.len() + 1;
|
let number_of_groups = self.manipulator_groups.len() + 1;
|
||||||
self.manipulator_groups.insert((segment_index) + 1, new_group);
|
self.manipulator_groups.insert((segment_index) + 1, new_group);
|
||||||
@@ -89,7 +89,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: bezier.start(),
|
anchor: bezier.start(),
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
let mut last_index = self.manipulator_groups.len() - 1;
|
let mut last_index = self.manipulator_groups.len() - 1;
|
||||||
@@ -114,7 +114,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: bezier.end(),
|
anchor: bezier.end(),
|
||||||
in_handle: bezier.handle_end(),
|
in_handle: bezier.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,25 +15,25 @@ use std::ops::{Index, IndexMut};
|
|||||||
/// Structure used to represent a path composed of [Bezier] curves.
|
/// Structure used to represent a path composed of [Bezier] curves.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Subpath<ManipulatorGroupId: crate::Identifier> {
|
pub struct Subpath<PointId: crate::Identifier> {
|
||||||
manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>>,
|
manipulator_groups: Vec<ManipulatorGroup<PointId>>,
|
||||||
pub closed: bool,
|
pub closed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dyn-any")]
|
#[cfg(feature = "dyn-any")]
|
||||||
unsafe impl<ManipulatorGroupId: crate::Identifier> dyn_any::StaticType for Subpath<ManipulatorGroupId> {
|
unsafe impl<PointId: crate::Identifier> dyn_any::StaticType for Subpath<PointId> {
|
||||||
type Static = Subpath<ManipulatorGroupId>;
|
type Static = Subpath<PointId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
|
/// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation.
|
||||||
pub struct SubpathIter<'a, ManipulatorGroupId: crate::Identifier> {
|
pub struct SubpathIter<'a, PointId: crate::Identifier> {
|
||||||
index: usize,
|
index: usize,
|
||||||
subpath: &'a Subpath<ManipulatorGroupId>,
|
subpath: &'a Subpath<PointId>,
|
||||||
is_always_closed: bool,
|
is_always_closed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Index<usize> for Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Index<usize> for Subpath<PointId> {
|
||||||
type Output = ManipulatorGroup<ManipulatorGroupId>;
|
type Output = ManipulatorGroup<PointId>;
|
||||||
|
|
||||||
fn index(&self, index: usize) -> &Self::Output {
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
|
assert!(index < self.len(), "Index out of bounds in trait Index of SubPath.");
|
||||||
@@ -41,14 +41,14 @@ impl<ManipulatorGroupId: crate::Identifier> Index<usize> for Subpath<Manipulator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> IndexMut<usize> for Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> IndexMut<usize> for Subpath<PointId> {
|
||||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
|
assert!(index < self.len(), "Index out of bounds in trait IndexMut of SubPath.");
|
||||||
&mut self.manipulator_groups[index]
|
&mut self.manipulator_groups[index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Iterator for SubpathIter<'_, ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Iterator for SubpathIter<'_, PointId> {
|
||||||
type Item = Bezier;
|
type Item = Bezier;
|
||||||
|
|
||||||
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
|
// Returns the Bezier representation of each `Subpath` segment, defined between a pair of adjacent manipulator points.
|
||||||
@@ -73,7 +73,7 @@ impl<ManipulatorGroupId: crate::Identifier> Iterator for SubpathIter<'_, Manipul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Debug for Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Debug for Subpath<PointId> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
f.debug_struct("Subpath").field("closed", &self.closed).field("manipulator_groups", &self.manipulator_groups).finish()
|
f.debug_struct("Subpath").field("closed", &self.closed).field("manipulator_groups", &self.manipulator_groups).finish()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::TValue;
|
|||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
/// Calculate the point on the subpath based on the parametric `t`-value provided.
|
/// Calculate the point on the subpath based on the parametric `t`-value provided.
|
||||||
/// Expects `t` to be within the inclusive range `[0, 1]`.
|
/// Expects `t` to be within the inclusive range `[0, 1]`.
|
||||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/evaluate/solo" title="Evaluate Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/evaluate/solo" title="Evaluate Demo"></iframe>
|
||||||
@@ -39,7 +39,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// This function expects the following:
|
/// This function expects the following:
|
||||||
/// - other: a [Bezier] curve to check intersections against
|
/// - other: a [Bezier] curve to check intersections against
|
||||||
/// - error: an optional f64 value to provide an error bound
|
/// - error: an optional f64 value to provide an error bound
|
||||||
pub fn subpath_intersections(&self, other: &Subpath<ManipulatorGroupId>, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
pub fn subpath_intersections(&self, other: &Subpath<PointId>, error: Option<f64>, minimum_separation: Option<f64>) -> Vec<(usize, f64)> {
|
||||||
let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect();
|
let mut intersection_t_values: Vec<(usize, f64)> = other.iter().flat_map(|bezier| self.intersections(&bezier, error, minimum_separation)).collect();
|
||||||
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
intersection_t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||||
intersection_t_values
|
intersection_t_values
|
||||||
@@ -302,6 +302,18 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>() != 0
|
self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>() != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Does a path contain a point? Based on the non zero winding. Automatically adds a linear segment if the subpath is not closed.
|
||||||
|
pub fn contains_point_autoclose(&self, target_point: DVec2) -> bool {
|
||||||
|
let mut winding = self.iter().map(|bezier| bezier.winding(target_point)).sum::<i32>();
|
||||||
|
if !self.closed {
|
||||||
|
if let [Some(first), Some(last)] = [self.manipulator_groups.first(), self.manipulator_groups.last()] {
|
||||||
|
winding += Bezier::from_linear_dvec2(first.anchor, last.anchor).winding(target_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winding != 0
|
||||||
|
}
|
||||||
|
|
||||||
/// Randomly places points across the filled surface of this subpath (which is assumed to be closed).
|
/// Randomly places points across the filled surface of this subpath (which is assumed to be closed).
|
||||||
/// The `separation_disk_diameter` determines the minimum distance between all points from one another.
|
/// The `separation_disk_diameter` determines the minimum distance between all points from one another.
|
||||||
/// Conceptually, this works by "throwing a dart" at the subpath's bounding box and keeping the dart only if:
|
/// Conceptually, this works by "throwing a dart" at the subpath's bounding box and keeping the dart only if:
|
||||||
@@ -342,7 +354,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// Alternatively, this can be interpreted as limiting the angle that the miter can form.
|
/// Alternatively, this can be interpreted as limiting the angle that the miter can form.
|
||||||
/// When the limit is exceeded, no manipulator group will be returned.
|
/// When the limit is exceeded, no manipulator group will be returned.
|
||||||
/// This value should be at least 1. If not, the default of 4 will be used.
|
/// This value should be at least 1. If not, the default of 4 will be used.
|
||||||
pub(crate) fn miter_line_join(&self, other: &Subpath<ManipulatorGroupId>, miter_limit: Option<f64>) -> Option<ManipulatorGroup<ManipulatorGroupId>> {
|
pub(crate) fn miter_line_join(&self, other: &Subpath<PointId>, miter_limit: Option<f64>) -> Option<ManipulatorGroup<PointId>> {
|
||||||
let miter_limit = match miter_limit {
|
let miter_limit = match miter_limit {
|
||||||
Some(miter_limit) if miter_limit >= 1. => miter_limit,
|
Some(miter_limit) if miter_limit >= 1. => miter_limit,
|
||||||
_ => 4.,
|
_ => 4.,
|
||||||
@@ -371,7 +383,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: intersection,
|
anchor: intersection,
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -384,7 +396,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// - The `out_handle` for the last manipulator group of `self`
|
/// - The `out_handle` for the last manipulator group of `self`
|
||||||
/// - The new manipulator group to be added
|
/// - The new manipulator group to be added
|
||||||
/// - The `in_handle` for the first manipulator group of `other`
|
/// - The `in_handle` for the first manipulator group of `other`
|
||||||
pub(crate) fn round_line_join(&self, other: &Subpath<ManipulatorGroupId>, center: DVec2) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
pub(crate) fn round_line_join(&self, other: &Subpath<PointId>, center: DVec2) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||||
let right = other.manipulator_groups[0].anchor;
|
let right = other.manipulator_groups[0].anchor;
|
||||||
|
|
||||||
@@ -410,7 +422,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// - The `out_handle` for the last manipulator group of `self`
|
/// - The `out_handle` for the last manipulator group of `self`
|
||||||
/// - The new manipulator group to be added
|
/// - The new manipulator group to be added
|
||||||
/// - The `in_handle` for the first manipulator group of `other`
|
/// - The `in_handle` for the first manipulator group of `other`
|
||||||
pub(crate) fn round_cap(&self, other: &Subpath<ManipulatorGroupId>) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
pub(crate) fn round_cap(&self, other: &Subpath<PointId>) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||||
let right = other.manipulator_groups[0].anchor;
|
let right = other.manipulator_groups[0].anchor;
|
||||||
|
|
||||||
@@ -423,7 +435,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the two manipulator groups that create a square cap between the end of `self` and the beginning of `other`.
|
/// Returns the two manipulator groups that create a square cap between the end of `self` and the beginning of `other`.
|
||||||
pub(crate) fn square_cap(&self, other: &Subpath<ManipulatorGroupId>) -> [ManipulatorGroup<ManipulatorGroupId>; 2] {
|
pub(crate) fn square_cap(&self, other: &Subpath<PointId>) -> [ManipulatorGroup<PointId>; 2] {
|
||||||
let left = self.manipulator_groups[self.len() - 1].anchor;
|
let left = self.manipulator_groups[self.len() - 1].anchor;
|
||||||
let right = other.manipulator_groups[0].anchor;
|
let right = other.manipulator_groups[0].anchor;
|
||||||
|
|
||||||
|
|||||||
@@ -26,15 +26,15 @@ impl Identifier for EmptyId {
|
|||||||
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
/// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath`
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ManipulatorGroup<ManipulatorGroupId: crate::Identifier> {
|
pub struct ManipulatorGroup<PointId: crate::Identifier> {
|
||||||
pub anchor: DVec2,
|
pub anchor: DVec2,
|
||||||
pub in_handle: Option<DVec2>,
|
pub in_handle: Option<DVec2>,
|
||||||
pub out_handle: Option<DVec2>,
|
pub out_handle: Option<DVec2>,
|
||||||
pub id: ManipulatorGroupId,
|
pub id: PointId,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove once we no longer need to hash floats in Graphite
|
// TODO: Remove once we no longer need to hash floats in Graphite
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Hash for ManipulatorGroup<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Hash for ManipulatorGroup<PointId> {
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state));
|
||||||
self.in_handle.is_some().hash(state);
|
self.in_handle.is_some().hash(state);
|
||||||
@@ -50,11 +50,11 @@ impl<ManipulatorGroupId: crate::Identifier> Hash for ManipulatorGroup<Manipulato
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "dyn-any")]
|
#[cfg(feature = "dyn-any")]
|
||||||
unsafe impl<ManipulatorGroupId: crate::Identifier> dyn_any::StaticType for ManipulatorGroup<ManipulatorGroupId> {
|
unsafe impl<PointId: crate::Identifier> dyn_any::StaticType for ManipulatorGroup<PointId> {
|
||||||
type Static = ManipulatorGroup<ManipulatorGroupId>;
|
type Static = ManipulatorGroup<PointId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Debug for ManipulatorGroup<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Debug for ManipulatorGroup<PointId> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||||
f.debug_struct("ManipulatorGroup")
|
f.debug_struct("ManipulatorGroup")
|
||||||
.field("anchor", &self.anchor)
|
.field("anchor", &self.anchor)
|
||||||
@@ -64,10 +64,10 @@ impl<ManipulatorGroupId: crate::Identifier> Debug for ManipulatorGroup<Manipulat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ManipulatorGroupId: crate::Identifier> ManipulatorGroup<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> ManipulatorGroup<PointId> {
|
||||||
/// Construct a new manipulator group from an anchor, in handle and out handle
|
/// Construct a new manipulator group from an anchor, in handle and out handle
|
||||||
pub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {
|
pub fn new(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>) -> Self {
|
||||||
let id = ManipulatorGroupId::new();
|
let id = PointId::new();
|
||||||
Self { anchor, in_handle, out_handle, id }
|
Self { anchor, in_handle, out_handle, id }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,17 +77,17 @@ impl<ManipulatorGroupId: crate::Identifier> ManipulatorGroup<ManipulatorGroupId>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new manipulator group from an anchor, in handle, out handle and an id
|
/// Construct a new manipulator group from an anchor, in handle, out handle and an id
|
||||||
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: ManipulatorGroupId) -> Self {
|
pub fn new_with_id(anchor: DVec2, in_handle: Option<DVec2>, out_handle: Option<DVec2>, id: PointId) -> Self {
|
||||||
Self { anchor, in_handle, out_handle, id }
|
Self { anchor, in_handle, out_handle, id }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a new manipulator point with just an anchor position and an id
|
/// Construct a new manipulator point with just an anchor position and an id
|
||||||
pub fn new_anchor_with_id(anchor: DVec2, id: ManipulatorGroupId) -> Self {
|
pub fn new_anchor_with_id(anchor: DVec2, id: PointId) -> Self {
|
||||||
Self::new_with_id(anchor, Some(anchor), Some(anchor), id)
|
Self::new_with_id(anchor, Some(anchor), Some(anchor), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.
|
/// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group.
|
||||||
pub fn to_bezier(&self, end_group: &ManipulatorGroup<ManipulatorGroupId>) -> Bezier {
|
pub fn to_bezier(&self, end_group: &ManipulatorGroup<PointId>) -> Bezier {
|
||||||
let start = self.anchor;
|
let start = self.anchor;
|
||||||
let end = end_group.anchor;
|
let end = end_group.anchor;
|
||||||
let out_handle = self.out_handle;
|
let out_handle = self.out_handle;
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ fn map_index_within_range(index: usize, t: f64, max_size: usize) -> (usize, f64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
/// Functionality that transforms Subpaths, such as split, reduce, offset, etc.
|
||||||
impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||||
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
|
/// Returns either one or two Subpaths that result from splitting the original Subpath at the point corresponding to `t`.
|
||||||
/// If the original Subpath was closed, a single open Subpath will be returned.
|
/// If the original Subpath was closed, a single open Subpath will be returned.
|
||||||
/// If the original Subpath was open, two open Subpaths will be returned.
|
/// If the original Subpath was open, two open Subpaths will be returned.
|
||||||
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/split/solo" title="Split Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="350px" src="https://graphite.rs/libraries/bezier-rs#subpath/split/solo" title="Split Demo"></iframe>
|
||||||
pub fn split(&self, t: SubpathTValue) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
|
pub fn split(&self, t: SubpathTValue) -> (Subpath<PointId>, Option<Subpath<PointId>>) {
|
||||||
let (segment_index, t) = self.t_value_to_parametric(t);
|
let (segment_index, t) = self.t_value_to_parametric(t);
|
||||||
let curve = self.get_segment(segment_index).unwrap();
|
let curve = self.get_segment(segment_index).unwrap();
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: first_bezier.end(),
|
anchor: first_bezier.end(),
|
||||||
in_handle: last_curve.handle_end(),
|
in_handle: last_curve.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if !first_split.is_empty() {
|
if !first_split.is_empty() {
|
||||||
@@ -68,7 +68,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: first_bezier.end(),
|
anchor: first_bezier.end(),
|
||||||
in_handle: first_bezier.handle_end(),
|
in_handle: first_bezier.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: second_bezier.start(),
|
anchor: second_bezier.start(),
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: second_bezier.handle_start(),
|
out_handle: second_bezier.handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns [ManipulatorGroup]s with a reversed winding order.
|
/// Returns [ManipulatorGroup]s with a reversed winding order.
|
||||||
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<ManipulatorGroupId>]) -> Vec<ManipulatorGroup<ManipulatorGroupId>> {
|
fn reverse_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>]) -> Vec<ManipulatorGroup<PointId>> {
|
||||||
manipulator_groups
|
manipulator_groups
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
@@ -103,14 +103,14 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: group.anchor,
|
anchor: group.anchor,
|
||||||
in_handle: group.out_handle,
|
in_handle: group.out_handle,
|
||||||
out_handle: group.in_handle,
|
out_handle: group.in_handle,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
|
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a [Subpath] with a reversed winding order.
|
/// Returns a [Subpath] with a reversed winding order.
|
||||||
/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction
|
/// Note that a reversed closed subpath will start on the same manipulator group and simply wind the other direction
|
||||||
pub fn reverse(&self) -> Subpath<ManipulatorGroupId> {
|
pub fn reverse(&self) -> Subpath<PointId> {
|
||||||
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
|
let mut reversed = Subpath::reverse_manipulator_groups(self.manipulator_groups());
|
||||||
if self.closed {
|
if self.closed {
|
||||||
reversed.rotate_right(1);
|
reversed.rotate_right(1);
|
||||||
@@ -127,7 +127,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`.
|
/// That means, if the value of `t1` > `t2`, it will cross the break between endpoints from `t1` to `t = 1 = 0` to `t2`.
|
||||||
/// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`.
|
/// If a path winding in the reverse direction is desired, call `trim` on the `Subpath` returned from `Subpath::reverse`.
|
||||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/trim/solo" title="Trim Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/trim/solo" title="Trim Demo"></iframe>
|
||||||
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<ManipulatorGroupId> {
|
pub fn trim(&self, t1: SubpathTValue, t2: SubpathTValue) -> Subpath<PointId> {
|
||||||
// Return a clone of the Subpath if it is not long enough to be a valid Bezier
|
// Return a clone of the Subpath if it is not long enough to be a valid Bezier
|
||||||
if self.manipulator_groups.is_empty() {
|
if self.manipulator_groups.is_empty() {
|
||||||
return Subpath {
|
return Subpath {
|
||||||
@@ -196,7 +196,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
|
|
||||||
cloned_manipulator_groups
|
cloned_manipulator_groups
|
||||||
.drain(range_start..range_end.min(cloned_manipulator_groups.len()))
|
.drain(range_start..range_end.min(cloned_manipulator_groups.len()))
|
||||||
.collect::<Vec<ManipulatorGroup<ManipulatorGroupId>>>()
|
.collect::<Vec<ManipulatorGroup<PointId>>>()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adjust curve indices to match the cloned list
|
// Adjust curve indices to match the cloned list
|
||||||
@@ -255,7 +255,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: front_split.start(),
|
anchor: front_split.start(),
|
||||||
in_handle: None,
|
in_handle: None,
|
||||||
out_handle: front_split.handle_start(),
|
out_handle: front_split.handle_start(),
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update the last two manipulator groups to match the back_split
|
// Update the last two manipulator groups to match the back_split
|
||||||
@@ -264,7 +264,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
anchor: back_split.end(),
|
anchor: back_split.end(),
|
||||||
in_handle: back_split.handle_end(),
|
in_handle: back_split.handle_end(),
|
||||||
out_handle: None,
|
out_handle: None,
|
||||||
id: ManipulatorGroupId::new(),
|
id: PointId::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Subpath {
|
Subpath {
|
||||||
@@ -313,7 +313,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
// at the incorrect location. This can be avoided by first trimming the two Subpaths at any extrema, effectively ignoring loopbacks.
|
// at the incorrect location. This can be avoided by first trimming the two Subpaths at any extrema, effectively ignoring loopbacks.
|
||||||
/// Helper function to clip overlap of two intersecting open Subpaths. Returns an optional, as intersections may not exist for certain arrangements and distances.
|
/// Helper function to clip overlap of two intersecting open Subpaths. Returns an optional, as intersections may not exist for certain arrangements and distances.
|
||||||
/// Assumes that the Subpaths represents simple Bezier segments, and clips the Subpaths at the last intersection of the first Subpath, and first intersection of the last Subpath.
|
/// Assumes that the Subpaths represents simple Bezier segments, and clips the Subpaths at the last intersection of the first Subpath, and first intersection of the last Subpath.
|
||||||
fn clip_simple_subpaths(subpath1: &Subpath<ManipulatorGroupId>, subpath2: &Subpath<ManipulatorGroupId>) -> Option<(Subpath<ManipulatorGroupId>, Subpath<ManipulatorGroupId>)> {
|
fn clip_simple_subpaths(subpath1: &Subpath<PointId>, subpath2: &Subpath<PointId>) -> Option<(Subpath<PointId>, Subpath<PointId>)> {
|
||||||
// Split the first subpath at its last intersection
|
// Split the first subpath at its last intersection
|
||||||
let intersections1 = subpath1.subpath_intersections(subpath2, None, None);
|
let intersections1 = subpath1.subpath_intersections(subpath2, None, None);
|
||||||
if intersections1.is_empty() {
|
if intersections1.is_empty() {
|
||||||
@@ -335,7 +335,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
|
|
||||||
/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).
|
/// Returns a subpath that results from rotating this subpath around the origin by the given angle (in radians).
|
||||||
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#subpath/rotate/solo" title="Rotate Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="325px" src="https://graphite.rs/libraries/bezier-rs#subpath/rotate/solo" title="Rotate Demo"></iframe>
|
||||||
pub fn rotate(&self, angle: f64) -> Subpath<ManipulatorGroupId> {
|
pub fn rotate(&self, angle: f64) -> Subpath<PointId> {
|
||||||
let mut rotated_subpath = self.clone();
|
let mut rotated_subpath = self.clone();
|
||||||
|
|
||||||
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
|
let affine_transform: DAffine2 = DAffine2::from_angle(angle);
|
||||||
@@ -345,7 +345,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).
|
/// Returns a subpath that results from rotating this subpath around the provided point by the given angle (in radians).
|
||||||
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<ManipulatorGroupId> {
|
pub fn rotate_about_point(&self, angle: f64, pivot: DVec2) -> Subpath<PointId> {
|
||||||
// Translate before and after the rotation to account for the pivot
|
// Translate before and after the rotation to account for the pivot
|
||||||
let translate: DAffine2 = DAffine2::from_translation(pivot);
|
let translate: DAffine2 = DAffine2::from_translation(pivot);
|
||||||
let rotate: DAffine2 = DAffine2::from_angle(angle);
|
let rotate: DAffine2 = DAffine2::from_angle(angle);
|
||||||
@@ -359,7 +359,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// Reduces the segments of the subpath into simple subcurves, then scales each subcurve a set `distance` away.
|
/// Reduces the segments of the subpath into simple subcurves, then scales each subcurve a set `distance` away.
|
||||||
/// The intersections of segments of the subpath are joined using the method specified by the `join` argument.
|
/// The intersections of segments of the subpath are joined using the method specified by the `join` argument.
|
||||||
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/offset/solo" title="Offset Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="400px" src="https://graphite.rs/libraries/bezier-rs#subpath/offset/solo" title="Offset Demo"></iframe>
|
||||||
pub fn offset(&self, distance: f64, join: Join) -> Subpath<ManipulatorGroupId> {
|
pub fn offset(&self, distance: f64, join: Join) -> Subpath<PointId> {
|
||||||
assert!(self.len_segments() > 1, "Cannot offset an empty Subpath.");
|
assert!(self.len_segments() > 1, "Cannot offset an empty Subpath.");
|
||||||
|
|
||||||
// An offset at a distance 0 from the curve is simply the same curve
|
// An offset at a distance 0 from the curve is simply the same curve
|
||||||
@@ -368,11 +368,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
return self.clone();
|
return self.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut subpaths = self
|
let mut subpaths = self.iter().filter(|bezier| !bezier.is_point()).map(|bezier| bezier.offset(distance)).collect::<Vec<Subpath<PointId>>>();
|
||||||
.iter()
|
|
||||||
.filter(|bezier| !bezier.is_point())
|
|
||||||
.map(|bezier| bezier.offset(distance))
|
|
||||||
.collect::<Vec<Subpath<ManipulatorGroupId>>>();
|
|
||||||
let mut drop_common_point = vec![true; self.len()];
|
let mut drop_common_point = vec![true; self.len()];
|
||||||
|
|
||||||
// Clip or join consecutive Subpaths
|
// Clip or join consecutive Subpaths
|
||||||
@@ -489,8 +485,8 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function to combine the two offsets that make up an outline.
|
/// Helper function to combine the two offsets that make up an outline.
|
||||||
pub(crate) fn combine_outline(&self, other: &Subpath<ManipulatorGroupId>, cap: Cap) -> Subpath<ManipulatorGroupId> {
|
pub(crate) fn combine_outline(&self, other: &Subpath<PointId>, cap: Cap) -> Subpath<PointId> {
|
||||||
let mut result_manipulator_groups: Vec<ManipulatorGroup<ManipulatorGroupId>> = vec![];
|
let mut result_manipulator_groups: Vec<ManipulatorGroup<PointId>> = vec![];
|
||||||
result_manipulator_groups.extend_from_slice(self.manipulator_groups());
|
result_manipulator_groups.extend_from_slice(self.manipulator_groups());
|
||||||
match cap {
|
match cap {
|
||||||
Cap::Butt => {
|
Cap::Butt => {
|
||||||
@@ -527,7 +523,7 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
|
|||||||
/// - `distance` - The outline's distance from the curve.
|
/// - `distance` - The outline's distance from the curve.
|
||||||
/// - `join` - The join type used to cap the endpoints of open bezier curves, and join successive subpath segments.
|
/// - `join` - The join type used to cap the endpoints of open bezier curves, and join successive subpath segments.
|
||||||
/// <iframe frameBorder="0" width="100%" height="425px" src="https://graphite.rs/libraries/bezier-rs#subpath/outline/solo" title="Outline Demo"></iframe>
|
/// <iframe frameBorder="0" width="100%" height="425px" src="https://graphite.rs/libraries/bezier-rs#subpath/outline/solo" title="Outline Demo"></iframe>
|
||||||
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath<ManipulatorGroupId>, Option<Subpath<ManipulatorGroupId>>) {
|
pub fn outline(&self, distance: f64, join: Join, cap: Cap) -> (Subpath<PointId>, Option<Subpath<PointId>>) {
|
||||||
let is_point = self.is_point();
|
let is_point = self.is_point();
|
||||||
let (pos_offset, neg_offset) = if is_point {
|
let (pos_offset, neg_offset) = if is_point {
|
||||||
let point = self.manipulator_groups[0].anchor;
|
let point = self.manipulator_groups[0].anchor;
|
||||||
|
|||||||
@@ -266,13 +266,7 @@ pub fn scale_point_from_origin(point: DVec2, origin: DVec2, should_flip_directio
|
|||||||
|
|
||||||
/// Computes the necessary details to form a circular join from `left` to `right`, along a circle around `center`.
|
/// Computes the necessary details to form a circular join from `left` to `right`, along a circle around `center`.
|
||||||
/// By default, the angle is assumed to be 180 degrees.
|
/// By default, the angle is assumed to be 180 degrees.
|
||||||
pub fn compute_circular_subpath_details<ManipulatorGroupId: crate::Identifier>(
|
pub fn compute_circular_subpath_details<PointId: crate::Identifier>(left: DVec2, arc_point: DVec2, right: DVec2, center: DVec2, angle: Option<f64>) -> (DVec2, ManipulatorGroup<PointId>, DVec2) {
|
||||||
left: DVec2,
|
|
||||||
arc_point: DVec2,
|
|
||||||
right: DVec2,
|
|
||||||
center: DVec2,
|
|
||||||
angle: Option<f64>,
|
|
||||||
) -> (DVec2, ManipulatorGroup<ManipulatorGroupId>, DVec2) {
|
|
||||||
let center_to_arc_point = arc_point - center;
|
let center_to_arc_point = arc_point - center;
|
||||||
|
|
||||||
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
// Based on https://pomax.github.io/bezierinfo/#circles_cubic
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
#![doc(html_root_url = "http://docs.rs/const-default/1.0.0")]
|
#![doc(html_root_url = "http://docs.rs/const-default/1.0.0")]
|
||||||
#![cfg_attr(feature = "unstable-docs", feature(doc_cfg))]
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
#![allow(clippy::missing_safety_doc)]
|
#![allow(clippy::missing_safety_doc)]
|
||||||
#[cfg(feature = "alloc")]
|
#[cfg(feature = "alloc")]
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(feature = "derive")]
|
#[cfg(feature = "derive")]
|
||||||
#[cfg_attr(feature = "unstable-docs", doc(cfg(feature = "derive")))]
|
|
||||||
pub use dyn_any_derive::DynAny;
|
pub use dyn_any_derive::DynAny;
|
||||||
|
|
||||||
/// Implement this trait for your `dyn Trait` types for all `T: Trait`
|
/// Implement this trait for your `dyn Trait` types for all `T: Trait`
|
||||||
|
|||||||
@@ -12,15 +12,15 @@ homepage = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw
|
|||||||
repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw-rs"
|
repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/raw-rs"
|
||||||
documentation = "https://docs.rs/raw-rs"
|
documentation = "https://docs.rs/raw-rs"
|
||||||
|
|
||||||
[features]
|
|
||||||
raw-rs-tests = []
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitstream-io = "2.3.0"
|
bitstream-io = "2.3.0"
|
||||||
num_enum = "0.7.2"
|
num_enum = "0.7.2"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tag-derive = { path = "tag-derive" }
|
tag-derive = { path = "tag-derive" }
|
||||||
|
libraw-rs = { version = "0.0.4", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
libraw-rs = "0.0.4"
|
|
||||||
downloader = "0.2.7"
|
downloader = "0.2.7"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
raw-rs-tests = ["libraw-rs"]
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ use std::path::Path;
|
|||||||
use raw_rs::RawImage;
|
use raw_rs::RawImage;
|
||||||
|
|
||||||
use downloader::{Download, Downloader};
|
use downloader::{Download, Downloader};
|
||||||
use libraw::Processor;
|
|
||||||
|
|
||||||
const TEST_FILES: [&str; 3] = ["ILCE-7M3-ARW2.3.5-blossoms.arw", "ILCE-7RM4-ARW2.3.5-kestrel.arw", "ILCE-6000-ARW2.3.1-windsock.arw"];
|
const TEST_FILES: [&str; 3] = ["ILCE-7M3-ARW2.3.5-blossoms.arw", "ILCE-7RM4-ARW2.3.5-kestrel.arw", "ILCE-6000-ARW2.3.1-windsock.arw"];
|
||||||
const BASE_URL: &str = "https://static.graphite.rs/test-data/libraries/raw-rs/";
|
const BASE_URL: &str = "https://static.graphite.rs/test-data/libraries/raw-rs/";
|
||||||
const BASE_PATH: &str = "./tests/images";
|
const BASE_PATH: &str = "./tests/images";
|
||||||
|
|
||||||
#[cfg_attr(feature = "raw-rs-tests", test)]
|
#[test]
|
||||||
|
#[cfg(feature = "raw-rs-tests")]
|
||||||
fn test_images_match_with_libraw() {
|
fn test_images_match_with_libraw() {
|
||||||
download_images();
|
download_images();
|
||||||
|
|
||||||
@@ -54,6 +54,7 @@ fn test_images_match_with_libraw() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-rs-tests")]
|
||||||
fn download_images() {
|
fn download_images() {
|
||||||
let mut path = Path::new(BASE_PATH).to_owned();
|
let mut path = Path::new(BASE_PATH).to_owned();
|
||||||
let mut downloads: Vec<Download> = Vec::new();
|
let mut downloads: Vec<Download> = Vec::new();
|
||||||
@@ -74,8 +75,9 @@ fn download_images() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-rs-tests")]
|
||||||
fn test_raw_data(content: &[u8]) -> Result<RawImage, String> {
|
fn test_raw_data(content: &[u8]) -> Result<RawImage, String> {
|
||||||
let processor = Processor::new();
|
let processor = libraw::Processor::new();
|
||||||
let libraw_raw_image = processor.decode(content).unwrap();
|
let libraw_raw_image = processor.decode(content).unwrap();
|
||||||
|
|
||||||
let mut content = Cursor::new(content);
|
let mut content = Cursor::new(content);
|
||||||
@@ -147,8 +149,9 @@ fn test_raw_data(content: &[u8]) -> Result<RawImage, String> {
|
|||||||
Ok(raw_image)
|
Ok(raw_image)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "raw-rs-tests")]
|
||||||
fn test_final_image(content: &[u8], raw_image: RawImage) -> Result<(), String> {
|
fn test_final_image(content: &[u8], raw_image: RawImage) -> Result<(), String> {
|
||||||
let processor = Processor::new();
|
let processor = libraw::Processor::new();
|
||||||
let libraw_image = processor.process_8bit(content).unwrap();
|
let libraw_image = processor.process_8bit(content).unwrap();
|
||||||
|
|
||||||
let image = raw_rs::process_8bit(raw_image);
|
let image = raw_rs::process_8bit(raw_image);
|
||||||
|
|||||||
@@ -65,3 +65,7 @@ rand = { workspace = true, default-features = false, features = ["std_rng"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
# the spirv target is not in the list of common cfgs so must be added manually
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_arch, values("spirv"))'] }
|
||||||
|
|||||||
@@ -334,8 +334,14 @@ impl GraphicElementRendered for VectorData {
|
|||||||
|
|
||||||
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
|
||||||
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
|
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
|
||||||
click_targets.extend(self.region_bezier_paths().map(|(_, subpath)| ClickTarget { stroke_width, subpath }));
|
let filled = self.style.fill() != &crate::vector::style::Fill::None;
|
||||||
click_targets.extend(self.stroke_bezier_paths().map(|subpath| ClickTarget { stroke_width, subpath }));
|
let fill = |mut subpath: bezier_rs::Subpath<_>| {
|
||||||
|
if filled {
|
||||||
|
subpath.set_closed(true);
|
||||||
|
}
|
||||||
|
subpath
|
||||||
|
};
|
||||||
|
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_usvg_node(&self) -> usvg::Node {
|
fn to_usvg_node(&self) -> usvg::Node {
|
||||||
@@ -475,7 +481,7 @@ impl GraphicElementRendered for crate::ArtboardGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn contains_artboard(&self) -> bool {
|
fn contains_artboard(&self) -> bool {
|
||||||
self.artboards.len() > 0
|
!self.artboards.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -581,8 +581,9 @@ fn vibrance_node(color: Color, vibrance: f64) -> Color {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum RedGreenBlue {
|
pub enum RedGreenBlue {
|
||||||
|
#[default]
|
||||||
Red,
|
Red,
|
||||||
Green,
|
Green,
|
||||||
Blue,
|
Blue,
|
||||||
@@ -600,8 +601,9 @@ impl core::fmt::Display for RedGreenBlue {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum RedGreenBlueAlpha {
|
pub enum RedGreenBlueAlpha {
|
||||||
|
#[default]
|
||||||
Red,
|
Red,
|
||||||
Green,
|
Green,
|
||||||
Blue,
|
Blue,
|
||||||
@@ -621,8 +623,9 @@ impl core::fmt::Display for RedGreenBlueAlpha {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum NoiseType {
|
pub enum NoiseType {
|
||||||
|
#[default]
|
||||||
Perlin,
|
Perlin,
|
||||||
OpenSimplex2,
|
OpenSimplex2,
|
||||||
OpenSimplex2S,
|
OpenSimplex2S,
|
||||||
@@ -662,8 +665,9 @@ impl NoiseType {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum FractalType {
|
pub enum FractalType {
|
||||||
|
#[default]
|
||||||
None,
|
None,
|
||||||
FBm,
|
FBm,
|
||||||
Ridged,
|
Ridged,
|
||||||
@@ -700,8 +704,9 @@ impl FractalType {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum CellularDistanceFunction {
|
pub enum CellularDistanceFunction {
|
||||||
|
#[default]
|
||||||
Euclidean,
|
Euclidean,
|
||||||
EuclideanSq,
|
EuclideanSq,
|
||||||
Manhattan,
|
Manhattan,
|
||||||
@@ -732,9 +737,10 @@ impl CellularDistanceFunction {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum CellularReturnType {
|
pub enum CellularReturnType {
|
||||||
CellValue,
|
CellValue,
|
||||||
|
#[default]
|
||||||
Nearest,
|
Nearest,
|
||||||
NextNearest,
|
NextNearest,
|
||||||
Average,
|
Average,
|
||||||
@@ -773,8 +779,9 @@ impl CellularReturnType {
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum DomainWarpType {
|
pub enum DomainWarpType {
|
||||||
|
#[default]
|
||||||
None,
|
None,
|
||||||
OpenSimplex2,
|
OpenSimplex2,
|
||||||
OpenSimplex2Reduced,
|
OpenSimplex2Reduced,
|
||||||
@@ -867,8 +874,9 @@ fn channel_mixer_node(
|
|||||||
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum RelativeAbsolute {
|
pub enum RelativeAbsolute {
|
||||||
|
#[default]
|
||||||
Relative,
|
Relative,
|
||||||
Absolute,
|
Absolute,
|
||||||
}
|
}
|
||||||
@@ -885,8 +893,9 @@ impl core::fmt::Display for RelativeAbsolute {
|
|||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[cfg_attr(feature = "std", derive(specta::Type))]
|
#[cfg_attr(feature = "std", derive(specta::Type))]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
|
||||||
pub enum SelectiveColorChoice {
|
pub enum SelectiveColorChoice {
|
||||||
|
#[default]
|
||||||
Reds,
|
Reds,
|
||||||
Yellows,
|
Yellows,
|
||||||
Greens,
|
Greens,
|
||||||
|
|||||||
@@ -774,7 +774,7 @@ impl Color {
|
|||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
|
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
|
||||||
let gamma = self.to_gamma_srgb();
|
let gamma = self.to_gamma_srgb().to_gamma_srgb();
|
||||||
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
|
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,5 @@ pub struct TextGeneratorNode<Text, FontName, Size> {
|
|||||||
#[node_fn(TextGeneratorNode)]
|
#[node_fn(TextGeneratorNode)]
|
||||||
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
|
||||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||||
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
|
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ impl Font {
|
|||||||
Self { font_family, font_style }
|
Self { font_family, font_style }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Default for Font {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(crate::consts::DEFAULT_FONT_FAMILY.into(), crate::consts::DEFAULT_FONT_STYLE.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
|
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)]
|
||||||
pub struct FontCache {
|
pub struct FontCache {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::uuid::ManipulatorGroupId;
|
use crate::vector::PointId;
|
||||||
|
|
||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||||
|
|
||||||
@@ -7,13 +7,13 @@ use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
|
|||||||
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
||||||
|
|
||||||
struct Builder {
|
struct Builder {
|
||||||
current_subpath: Subpath<ManipulatorGroupId>,
|
current_subpath: Subpath<PointId>,
|
||||||
other_subpaths: Vec<Subpath<ManipulatorGroupId>>,
|
other_subpaths: Vec<Subpath<PointId>>,
|
||||||
pos: DVec2,
|
pos: DVec2,
|
||||||
offset: DVec2,
|
offset: DVec2,
|
||||||
ascender: f64,
|
ascender: f64,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
id: ManipulatorGroupId,
|
id: PointId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Builder {
|
impl Builder {
|
||||||
@@ -37,7 +37,7 @@ impl OutlineBuilder for Builder {
|
|||||||
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
|
||||||
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
|
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
|
||||||
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);
|
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);
|
||||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(anchor, self.id.next_id()));
|
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, None, None, self.id.next_id()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
|
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
|
||||||
@@ -80,7 +80,7 @@ fn wrap_word(line_width: Option<f64>, glyph_buffer: &GlyphBuffer, scale: f64, x_
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<ManipulatorGroupId>> {
|
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<PointId>> {
|
||||||
let buzz_face = match buzz_face {
|
let buzz_face = match buzz_face {
|
||||||
Some(face) => face,
|
Some(face) => face,
|
||||||
// Show blank layer if font has not loaded
|
// Show blank layer if font has not loaded
|
||||||
@@ -96,7 +96,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, li
|
|||||||
offset: DVec2::ZERO,
|
offset: DVec2::ZERO,
|
||||||
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
|
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
|
||||||
scale,
|
scale,
|
||||||
id: ManipulatorGroupId::ZERO,
|
id: PointId::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
for line in str.split('\n') {
|
for line in str.split('\n') {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, P
|
|||||||
pub(crate) rotate: Rotation,
|
pub(crate) rotate: Rotation,
|
||||||
pub(crate) scale: Scale,
|
pub(crate) scale: Scale,
|
||||||
pub(crate) shear: Shear,
|
pub(crate) shear: Shear,
|
||||||
pub(crate) pivot: Pivot,
|
pub(crate) _pivot: Pivot,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
|
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
|
||||||
@@ -165,7 +165,7 @@ pub enum RenderQuality {
|
|||||||
Scale(f32),
|
Scale(f32),
|
||||||
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
|
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
|
||||||
/// This should be used to gradually update the render quality of a cached node
|
/// This should be used to gradually update the render quality of a cached node
|
||||||
Probabilty(f32),
|
Probability(f32),
|
||||||
/// Render at full quality
|
/// Render at full quality
|
||||||
Full,
|
Full,
|
||||||
}
|
}
|
||||||
@@ -249,23 +249,18 @@ pub(crate) async fn transform_vector_data<Fut: Future>(
|
|||||||
rotate: f64,
|
rotate: f64,
|
||||||
scale: DVec2,
|
scale: DVec2,
|
||||||
shear: DVec2,
|
shear: DVec2,
|
||||||
pivot: DVec2,
|
_pivot: DVec2,
|
||||||
) -> Fut::Output
|
) -> Fut::Output
|
||||||
where
|
where
|
||||||
Fut::Output: TransformMut,
|
Fut::Output: TransformMut,
|
||||||
{
|
{
|
||||||
// TODO: This is hack and might break for Vector data because the pivot may be incorrect
|
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||||
let transform = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
|
||||||
if !footprint.ignore_modifications {
|
if !footprint.ignore_modifications {
|
||||||
let pivot_transform = DAffine2::from_translation(pivot);
|
|
||||||
let modification = pivot_transform * transform * pivot_transform.inverse();
|
|
||||||
*footprint.transform_mut() = footprint.transform() * modification;
|
*footprint.transform_mut() = footprint.transform() * modification;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut data = self.transform_target.eval(footprint).await;
|
let mut data = self.transform_target.eval(footprint).await;
|
||||||
let pivot_transform = DAffine2::from_translation(data.local_pivot(pivot));
|
|
||||||
|
|
||||||
let modification = pivot_transform * transform * pivot_transform.inverse();
|
|
||||||
let data_transform = data.transform_mut();
|
let data_transform = data.transform_mut();
|
||||||
*data_transform = modification * (*data_transform);
|
*data_transform = modification * (*data_transform);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use dyn_any::{DynAny, StaticType};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
pub struct Uuid(
|
pub struct Uuid(
|
||||||
#[serde(with = "u64_string")]
|
#[serde(with = "u64_string")]
|
||||||
@@ -69,33 +67,3 @@ mod uuid_generation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use uuid_generation::*;
|
pub use uuid_generation::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
pub struct ManipulatorGroupId(u64);
|
|
||||||
|
|
||||||
impl bezier_rs::Identifier for ManipulatorGroupId {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self(generate_uuid())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ManipulatorGroupId {
|
|
||||||
pub const ZERO: ManipulatorGroupId = ManipulatorGroupId(0);
|
|
||||||
|
|
||||||
pub fn next_id(&mut self) -> Self {
|
|
||||||
let old = self.0;
|
|
||||||
self.0 += 1;
|
|
||||||
Self(old)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn inner(self) -> u64 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<crate::vector::PointId> for ManipulatorGroupId {
|
|
||||||
fn from(value: crate::vector::PointId) -> Self {
|
|
||||||
Self(value.inner())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::uuid::ManipulatorGroupId;
|
use super::HandleId;
|
||||||
use crate::vector::VectorData;
|
use crate::vector::{PointId, VectorData};
|
||||||
use crate::Node;
|
use crate::Node;
|
||||||
|
|
||||||
use bezier_rs::Subpath;
|
use bezier_rs::Subpath;
|
||||||
@@ -28,7 +28,14 @@ fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
|
|||||||
let radius = DVec2::new(radius_x, radius_y);
|
let radius = DVec2::new(radius_x, radius_y);
|
||||||
let corner1 = -radius;
|
let corner1 = -radius;
|
||||||
let corner2 = radius;
|
let corner2 = radius;
|
||||||
super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2))
|
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
|
||||||
|
let len = ellipse.segment_domain.ids().len();
|
||||||
|
for i in 0..len {
|
||||||
|
ellipse
|
||||||
|
.colinear_manipulators
|
||||||
|
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
|
||||||
|
}
|
||||||
|
ellipse
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -46,7 +53,7 @@ trait CornerRadius {
|
|||||||
impl CornerRadius for f64 {
|
impl CornerRadius for f64 {
|
||||||
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
|
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
|
||||||
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
|
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
|
||||||
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])])
|
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CornerRadius for [f64; 4] {
|
impl CornerRadius for [f64; 4] {
|
||||||
@@ -66,7 +73,7 @@ impl CornerRadius for [f64; 4] {
|
|||||||
} else {
|
} else {
|
||||||
self
|
self
|
||||||
};
|
};
|
||||||
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)])
|
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +129,11 @@ pub struct SplineGenerator<Positions> {
|
|||||||
|
|
||||||
#[node_macro::node_fn(SplineGenerator)]
|
#[node_macro::node_fn(SplineGenerator)]
|
||||||
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
|
||||||
super::VectorData::from_subpath(Subpath::new_cubic_spline(positions))
|
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
|
||||||
|
for pair in spline.segment_domain.ids().windows(2) {
|
||||||
|
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
|
||||||
|
}
|
||||||
|
spline
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
|
||||||
@@ -132,9 +143,12 @@ pub struct PathGenerator<ColinearManipulators> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(PathGenerator)]
|
#[node_macro::node_fn(PathGenerator)]
|
||||||
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, colinear_manipulators: Vec<ManipulatorGroupId>) -> super::VectorData {
|
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
|
||||||
let mut vector_data = super::VectorData::from_subpaths(path_data);
|
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
|
||||||
vector_data.colinear_manipulators = colinear_manipulators;
|
vector_data.colinear_manipulators = colinear_manipulators
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
|
||||||
|
.collect();
|
||||||
vector_data
|
vector_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -371,16 +371,18 @@ impl From<Fill> for FillChoice {
|
|||||||
|
|
||||||
/// Enum describing the type of [Fill].
|
/// Enum describing the type of [Fill].
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
|
||||||
pub enum FillType {
|
pub enum FillType {
|
||||||
|
#[default]
|
||||||
Solid,
|
Solid,
|
||||||
Gradient,
|
Gradient,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The stroke (outline) style of an SVG element.
|
/// The stroke (outline) style of an SVG element.
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
||||||
pub enum LineCap {
|
pub enum LineCap {
|
||||||
|
#[default]
|
||||||
Butt,
|
Butt,
|
||||||
Round,
|
Round,
|
||||||
Square,
|
Square,
|
||||||
@@ -397,8 +399,9 @@ impl Display for LineCap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
|
||||||
pub enum LineJoin {
|
pub enum LineJoin {
|
||||||
|
#[default]
|
||||||
Miter,
|
Miter,
|
||||||
Bevel,
|
Bevel,
|
||||||
Round,
|
Round,
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
mod attributes;
|
mod attributes;
|
||||||
|
mod modification;
|
||||||
|
pub use attributes::*;
|
||||||
|
pub use modification::*;
|
||||||
|
|
||||||
use super::style::{PathStyle, Stroke};
|
use super::style::{PathStyle, Stroke};
|
||||||
use crate::Color;
|
use crate::{AlphaBlending, Color};
|
||||||
use crate::{uuid::ManipulatorGroupId, AlphaBlending};
|
|
||||||
pub use attributes::*;
|
|
||||||
|
|
||||||
use bezier_rs::ManipulatorGroup;
|
use bezier_rs::ManipulatorGroup;
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
|
|
||||||
|
use core::borrow::Borrow;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
/// [VectorData] is passed between nodes.
|
/// [VectorData] is passed between nodes.
|
||||||
@@ -20,7 +22,7 @@ pub struct VectorData {
|
|||||||
pub alpha_blending: AlphaBlending,
|
pub alpha_blending: AlphaBlending,
|
||||||
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
|
||||||
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
|
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
|
||||||
pub colinear_manipulators: Vec<ManipulatorGroupId>,
|
pub colinear_manipulators: Vec<[HandleId; 2]>,
|
||||||
|
|
||||||
pub point_domain: PointDomain,
|
pub point_domain: PointDomain,
|
||||||
pub segment_domain: SegmentDomain,
|
pub segment_domain: SegmentDomain,
|
||||||
@@ -54,15 +56,15 @@ impl VectorData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct some new vector data from a single subpath with an identity transform and black fill.
|
/// Construct some new vector data from a single subpath with an identity transform and black fill.
|
||||||
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
|
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
|
||||||
Self::from_subpaths([subpath])
|
Self::from_subpaths([subpath], false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a subpath to the vector data
|
/// Push a subpath to the vector data
|
||||||
pub fn append_subpath<Id: bezier_rs::Identifier + Into<PointId> + Copy>(&mut self, subpath: bezier_rs::Subpath<Id>) {
|
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
|
||||||
for point in subpath.manipulator_groups() {
|
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
|
||||||
self.point_domain.push(point.id.into(), point.anchor);
|
let stroke_id = StrokeId::ZERO;
|
||||||
}
|
let mut point_id = self.point_domain.next_id();
|
||||||
|
|
||||||
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
|
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
|
||||||
(None, None) => bezier_rs::BezierHandles::Linear,
|
(None, None) => bezier_rs::BezierHandles::Linear,
|
||||||
@@ -70,33 +72,57 @@ impl VectorData {
|
|||||||
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||||
};
|
};
|
||||||
let [mut first_seg, mut last_seg] = [None, None];
|
let [mut first_seg, mut last_seg] = [None, None];
|
||||||
|
let mut segment_id = self.segment_domain.next_id();
|
||||||
|
let mut last_point = None;
|
||||||
|
let mut first_point = None;
|
||||||
for pair in subpath.manipulator_groups().windows(2) {
|
for pair in subpath.manipulator_groups().windows(2) {
|
||||||
let id = SegmentId::generate();
|
let start = last_point.unwrap_or_else(|| {
|
||||||
|
let id = if preserve_id && !self.point_domain.ids().contains(&pair[0].id) {
|
||||||
|
pair[0].id
|
||||||
|
} else {
|
||||||
|
point_id.next_id()
|
||||||
|
};
|
||||||
|
self.point_domain.push(id, pair[0].anchor);
|
||||||
|
id
|
||||||
|
});
|
||||||
|
first_point = Some(first_point.unwrap_or(start));
|
||||||
|
let end = if preserve_id && !self.point_domain.ids().contains(&pair[1].id) {
|
||||||
|
pair[1].id
|
||||||
|
} else {
|
||||||
|
point_id.next_id()
|
||||||
|
};
|
||||||
|
self.point_domain.push(end, pair[1].anchor);
|
||||||
|
|
||||||
|
let id = segment_id.next_id();
|
||||||
first_seg = Some(first_seg.unwrap_or(id));
|
first_seg = Some(first_seg.unwrap_or(id));
|
||||||
last_seg = Some(id);
|
last_seg = Some(id);
|
||||||
self.segment_domain.push(id, pair[0].id.into(), pair[1].id.into(), handles(&pair[0], &pair[1]), StrokeId::generate());
|
self.segment_domain.push(id, start, end, handles(&pair[0], &pair[1]), stroke_id);
|
||||||
|
|
||||||
|
last_point = Some(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fill_id = FillId::ZERO;
|
||||||
|
|
||||||
if subpath.closed() {
|
if subpath.closed() {
|
||||||
if let (Some(last), Some(first)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first()) {
|
if let (Some(last), Some(first), Some(first_id), Some(last_id)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first(), first_point, last_point) {
|
||||||
let id = SegmentId::generate();
|
let id = segment_id.next_id();
|
||||||
first_seg = Some(first_seg.unwrap_or(id));
|
first_seg = Some(first_seg.unwrap_or(id));
|
||||||
last_seg = Some(id);
|
last_seg = Some(id);
|
||||||
self.segment_domain.push(id, last.id.into(), first.id.into(), handles(last, first), StrokeId::generate());
|
self.segment_domain.push(id, last_id, first_id, handles(last, first), stroke_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] {
|
if let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] {
|
||||||
self.region_domain.push(RegionId::generate(), first_seg..=last_seg, FillId::generate());
|
self.region_domain.push(self.region_domain.next_id(), first_seg..=last_seg, fill_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct some new vector data from subpaths with an identity transform and black fill.
|
/// Construct some new vector data from subpaths with an identity transform and black fill.
|
||||||
pub fn from_subpaths(subpaths: impl IntoIterator<Item = bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
|
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
|
||||||
let mut vector_data = Self::empty();
|
let mut vector_data = Self::empty();
|
||||||
|
|
||||||
for subpath in subpaths.into_iter() {
|
for subpath in subpaths.into_iter() {
|
||||||
vector_data.append_subpath(subpath);
|
vector_data.append_subpath(subpath, preserve_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
vector_data
|
vector_data
|
||||||
@@ -142,6 +168,33 @@ impl VectorData {
|
|||||||
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
|
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
|
||||||
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
|
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Points connected to a single segment
|
||||||
|
pub fn single_connected_points(&self) -> impl Iterator<Item = PointId> + '_ {
|
||||||
|
self.point_domain.ids().iter().copied().filter(|&point| self.segment_domain.connected_count(point) == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle.
|
||||||
|
pub fn colinear(&self, point: ManipulatorPointId) -> bool {
|
||||||
|
let has_handle = |target| self.colinear_manipulators.iter().flatten().any(|&handle| handle == target);
|
||||||
|
match point {
|
||||||
|
ManipulatorPointId::Anchor(id) => {
|
||||||
|
self.segment_domain.start_connected(id).all(|segment| has_handle(HandleId::primary(segment))) && self.segment_domain.end_connected(id).all(|segment| has_handle(HandleId::end(segment)))
|
||||||
|
}
|
||||||
|
ManipulatorPointId::PrimaryHandle(segment) => has_handle(HandleId::primary(segment)),
|
||||||
|
ManipulatorPointId::EndHandle(segment) => has_handle(HandleId::end(segment)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId> {
|
||||||
|
let pair = self.colinear_manipulators.iter().find(|pair| pair.iter().any(|&val| val == handle))?;
|
||||||
|
let other = pair.iter().copied().find(|&val| val != handle)?;
|
||||||
|
if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {
|
||||||
|
Some(other)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VectorData {
|
impl Default for VectorData {
|
||||||
@@ -150,64 +203,192 @@ impl Default for VectorData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curviture).
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ManipulatorPointId {
|
pub enum ManipulatorPointId {
|
||||||
pub group: ManipulatorGroupId,
|
/// A control anchor - the start or end point of a bézier.
|
||||||
pub manipulator_type: SelectedType,
|
Anchor(PointId),
|
||||||
|
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
|
||||||
|
PrimaryHandle(SegmentId),
|
||||||
|
/// The end handle on a cubic bézier.
|
||||||
|
EndHandle(SegmentId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManipulatorPointId {
|
impl ManipulatorPointId {
|
||||||
pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self {
|
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
|
||||||
Self { group, manipulator_type }
|
#[must_use]
|
||||||
|
pub fn get_position(&self, vector_data: &VectorData) -> Option<DVec2> {
|
||||||
|
match self {
|
||||||
|
ManipulatorPointId::Anchor(id) => vector_data.point_domain.position_from_id(*id),
|
||||||
|
ManipulatorPointId::PrimaryHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
|
||||||
|
ManipulatorPointId::EndHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to get a pair of handles. For an anchor this is the first to handles connected. For a handle it is self and the first opposing handle.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
|
||||||
|
match self {
|
||||||
|
ManipulatorPointId::Anchor(point) => vector_data.segment_domain.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
|
||||||
|
ManipulatorPointId::PrimaryHandle(segment) => {
|
||||||
|
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
|
||||||
|
let current = HandleId::primary(segment);
|
||||||
|
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
|
||||||
|
other.map(|other| [current, other])
|
||||||
|
}
|
||||||
|
ManipulatorPointId::EndHandle(segment) => {
|
||||||
|
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
|
||||||
|
let current = HandleId::end(segment);
|
||||||
|
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
|
||||||
|
other.map(|other| [current, other])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
|
||||||
|
#[must_use]
|
||||||
|
pub fn get_anchor(self, vector_data: &VectorData) -> Option<PointId> {
|
||||||
|
match self {
|
||||||
|
ManipulatorPointId::Anchor(point) => Some(point),
|
||||||
|
ManipulatorPointId::PrimaryHandle(segment) => vector_data.segment_domain.segment_start_from_id(segment),
|
||||||
|
ManipulatorPointId::EndHandle(segment) => vector_data.segment_domain.segment_end_from_id(segment),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_handle(self) -> Option<HandleId> {
|
||||||
|
match self {
|
||||||
|
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
|
||||||
|
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
|
||||||
|
ManipulatorPointId::Anchor(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to convert self to an anchor, returning None for a handle.
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_anchor(self) -> Option<PointId> {
|
||||||
|
match self {
|
||||||
|
ManipulatorPointId::Anchor(point) => Some(point),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The type of handle found on a bézier curve.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum SelectedType {
|
pub enum HandleType {
|
||||||
Anchor = 1 << 0,
|
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||||
InHandle = 1 << 1,
|
Primary,
|
||||||
OutHandle = 1 << 2,
|
/// The second handle on a cubic bézier.
|
||||||
|
End,
|
||||||
}
|
}
|
||||||
impl SelectedType {
|
|
||||||
/// Get the location of the [SelectedType] in the [ManipulatorGroup]
|
/// Represents a primary or end handle found in a particular segment.
|
||||||
pub fn get_position(&self, manipulator_group: &ManipulatorGroup<ManipulatorGroupId>) -> Option<DVec2> {
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
|
||||||
match self {
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
Self::Anchor => Some(manipulator_group.anchor),
|
pub struct HandleId {
|
||||||
Self::InHandle => manipulator_group.in_handle,
|
pub ty: HandleType,
|
||||||
Self::OutHandle => manipulator_group.out_handle,
|
pub segment: SegmentId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HandleId {
|
||||||
|
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn primary(segment: SegmentId) -> Self {
|
||||||
|
Self { ty: HandleType::Primary, segment }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct a handle for the end handle on a cubic bézier.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn end(segment: SegmentId) -> Self {
|
||||||
|
Self { ty: HandleType::End, segment }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert to [`ManipulatorPointId`].
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_manipulator_point(self) -> ManipulatorPointId {
|
||||||
|
match self.ty {
|
||||||
|
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
|
||||||
|
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the closest [SelectedType] in the [ManipulatorGroup].
|
/// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle.
|
||||||
pub fn closest_widget(manipulator_group: &ManipulatorGroup<ManipulatorGroupId>, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) {
|
#[must_use]
|
||||||
let anchor = transform_space.transform_point2(manipulator_group.anchor);
|
pub fn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {
|
||||||
// Skip handles under the anchor
|
let Self { ty, segment } = self;
|
||||||
let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2);
|
match ty {
|
||||||
let compute_distance = |selected_type: Self| {
|
HandleType::Primary => VectorModificationType::SetPrimaryHandle { segment, relative_position },
|
||||||
selected_type.get_position(manipulator_group).and_then(|position| {
|
HandleType::End => VectorModificationType::SetEndHandle { segment, relative_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
|
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
|
||||||
pub fn is_handle(self) -> bool {
|
#[must_use]
|
||||||
self != SelectedType::Anchor
|
pub fn opposite(self) -> Self {
|
||||||
|
match self.ty {
|
||||||
|
HandleType::Primary => Self::end(self.segment),
|
||||||
|
HandleType::End => Self::primary(self.segment),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
fn assert_subpath_eq(generated: &Vec<bezier_rs::Subpath<PointId>>, expected: &[bezier_rs::Subpath<PointId>]) {
|
||||||
|
assert_eq!(generated.len(), expected.len());
|
||||||
|
for (generated, expected) in generated.iter().zip(expected) {
|
||||||
|
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
|
||||||
|
assert_eq!(generated.closed(), expected.closed());
|
||||||
|
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
|
||||||
|
assert_eq!(generated.in_handle, expected.in_handle);
|
||||||
|
assert_eq!(generated.out_handle, expected.out_handle);
|
||||||
|
assert_eq!(generated.anchor, expected.anchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn construct_closed_subpath() {
|
||||||
|
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
|
let vector_data = VectorData::from_subpath(&circle);
|
||||||
|
assert_eq!(vector_data.point_domain.ids().len(), 4);
|
||||||
|
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||||
|
assert_eq!(bézier_paths.len(), 4);
|
||||||
|
assert!(bézier_paths.iter().all(|&bézier| circle.iter().any(|original_bézier| original_bézier == bézier)));
|
||||||
|
|
||||||
|
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||||
|
assert_subpath_eq(&generated, &[circle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn construct_open_subpath() {
|
||||||
|
let bézier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||||
|
let subpath = bezier_rs::Subpath::from_bezier(&bézier);
|
||||||
|
let vector_data = VectorData::from_subpath(&subpath);
|
||||||
|
assert_eq!(vector_data.point_domain.ids().len(), 2);
|
||||||
|
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||||
|
assert_eq!(bézier_paths, vec![bézier]);
|
||||||
|
|
||||||
|
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||||
|
assert_subpath_eq(&generated, &[subpath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn construct_many_subpath() {
|
||||||
|
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||||
|
let curve = bezier_rs::Subpath::from_bezier(&curve);
|
||||||
|
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||||
|
|
||||||
|
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
|
||||||
|
assert_eq!(vector_data.point_domain.ids().len(), 6);
|
||||||
|
|
||||||
|
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||||
|
assert_eq!(bézier_paths.len(), 5);
|
||||||
|
assert!(bézier_paths.iter().all(|&bézier| circle.iter().chain(curve.iter()).any(|original_bézier| original_bézier == bézier)));
|
||||||
|
|
||||||
|
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||||
|
assert_subpath_eq(&generated, &[curve, circle]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
use super::HandleId;
|
||||||
|
|
||||||
use dyn_any::{DynAny, StaticType};
|
use dyn_any::{DynAny, StaticType};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
|
||||||
macro_rules! create_ids {
|
macro_rules! create_ids {
|
||||||
($($id:ident),*) => {
|
($($id:ident),*) => {
|
||||||
$(
|
$(
|
||||||
@@ -12,14 +15,23 @@ macro_rules! create_ids {
|
|||||||
pub struct $id(u64);
|
pub struct $id(u64);
|
||||||
|
|
||||||
impl $id {
|
impl $id {
|
||||||
|
pub const ZERO: $id = $id(0);
|
||||||
|
|
||||||
/// Generate a new random id
|
/// Generate a new random id
|
||||||
pub fn generate() -> Self {
|
pub fn generate() -> Self {
|
||||||
Self(crate::uuid::generate_uuid())
|
Self(crate::uuid::generate_uuid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the inner raw value.
|
||||||
pub fn inner(self) -> u64 {
|
pub fn inner(self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds one to the current value and returns the old value. Note that the ids are not going to be unique unless you use the largest id.
|
||||||
|
pub fn next_id(&mut self) -> Self {
|
||||||
|
self.0 += 1;
|
||||||
|
*self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
@@ -55,7 +67,17 @@ impl PointDomain {
|
|||||||
self.positions.clear();
|
self.positions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn retain(&mut self, f: impl Fn(&PointId) -> bool) {
|
||||||
|
let mut keep = self.id.iter().map(&f);
|
||||||
|
self.positions.retain(|_| keep.next().unwrap_or_default());
|
||||||
|
self.id.retain(f);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, id: PointId, position: DVec2) {
|
pub fn push(&mut self, id: PointId, position: DVec2) {
|
||||||
|
if self.id.contains(&id) {
|
||||||
|
warn!("Duplicate point");
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.id.push(id);
|
self.id.push(id);
|
||||||
self.positions.push(position);
|
self.positions.push(position);
|
||||||
}
|
}
|
||||||
@@ -64,11 +86,19 @@ impl PointDomain {
|
|||||||
&self.positions
|
&self.positions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {
|
||||||
|
self.id.iter().copied().zip(self.positions.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ids(&self) -> &[PointId] {
|
pub fn ids(&self) -> &[PointId] {
|
||||||
&self.id
|
&self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pos_from_id(&self, id: PointId) -> Option<DVec2> {
|
pub fn next_id(&self) -> PointId {
|
||||||
|
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(PointId::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn position_from_id(&self, id: PointId) -> Option<DVec2> {
|
||||||
let pos = self.resolve_id(id).map(|index| self.positions[index]);
|
let pos = self.resolve_id(id).map(|index| self.positions[index]);
|
||||||
if pos.is_none() {
|
if pos.is_none() {
|
||||||
warn!("Resolving pos of invalid id");
|
warn!("Resolving pos of invalid id");
|
||||||
@@ -99,7 +129,6 @@ pub struct SegmentDomain {
|
|||||||
ids: Vec<SegmentId>,
|
ids: Vec<SegmentId>,
|
||||||
start_point: Vec<PointId>,
|
start_point: Vec<PointId>,
|
||||||
end_point: Vec<PointId>,
|
end_point: Vec<PointId>,
|
||||||
// TODO: Also store handle points as `PointId`s rather than Bezier-rs's internal `DVec2`s
|
|
||||||
handles: Vec<bezier_rs::BezierHandles>,
|
handles: Vec<bezier_rs::BezierHandles>,
|
||||||
stroke: Vec<StrokeId>,
|
stroke: Vec<StrokeId>,
|
||||||
}
|
}
|
||||||
@@ -123,21 +152,116 @@ impl SegmentDomain {
|
|||||||
self.stroke.clear();
|
self.stroke.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
|
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool) {
|
||||||
self.ids.push(id);
|
let mut keep = self.ids.iter().map(&f);
|
||||||
self.start_point.push(start);
|
self.start_point.retain(|_| keep.next().unwrap_or_default());
|
||||||
self.end_point.push(end);
|
let mut keep = self.ids.iter().map(&f);
|
||||||
self.handles.push(handles);
|
self.end_point.retain(|_| keep.next().unwrap_or_default());
|
||||||
self.stroke.push(stroke);
|
let mut keep = self.ids.iter().map(&f);
|
||||||
|
self.handles.retain(|_| keep.next().unwrap_or_default());
|
||||||
|
let mut keep = self.ids.iter().map(&f);
|
||||||
|
self.stroke.retain(|_| keep.next().unwrap_or_default());
|
||||||
|
self.ids.retain(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_id(&self, id: SegmentId) -> Option<usize> {
|
pub fn ids(&self) -> &[SegmentId] {
|
||||||
|
&self.ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_id(&self) -> SegmentId {
|
||||||
|
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(SegmentId::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_point(&self) -> &[PointId] {
|
||||||
|
&self.start_point
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_point(&self) -> &[PointId] {
|
||||||
|
&self.end_point
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handles(&self) -> &[bezier_rs::BezierHandles] {
|
||||||
|
&self.handles
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stroke(&self) -> &[StrokeId] {
|
||||||
|
&self.stroke
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
|
||||||
|
if self.ids.contains(&id) {
|
||||||
|
warn!("Duplicate segment");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Attempt to keep line joins?
|
||||||
|
let after = self.end_point.iter().copied().position(|other_end| other_end == start || other_end == end);
|
||||||
|
let before = self.start_point.iter().copied().position(|other_start| other_start == start || other_start == end);
|
||||||
|
let (index, flip) = match (before, after) {
|
||||||
|
(_, Some(after)) => (after + 1, self.end_point[after] == end),
|
||||||
|
(Some(before), _) => (before, self.start_point[before] == start),
|
||||||
|
(None, None) => (self.ids.len(), false),
|
||||||
|
};
|
||||||
|
self.ids.insert(index, id);
|
||||||
|
self.start_point.insert(index, if flip { end } else { start });
|
||||||
|
self.end_point.insert(index, if flip { start } else { end });
|
||||||
|
self.handles.insert(index, if flip { handles.flipped() } else { handles });
|
||||||
|
self.stroke.insert(index, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
|
||||||
|
self.ids.iter().copied().zip(self.start_point.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
|
||||||
|
self.ids.iter().copied().zip(self.end_point.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
|
||||||
|
let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
|
||||||
|
nested.map(|(((&a, b), &c), &d)| (a, b, c, d))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {
|
||||||
|
self.ids.iter().copied().zip(self.stroke.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_start_from_id(&self, segment: SegmentId) -> Option<PointId> {
|
||||||
|
self.id_to_index(segment).and_then(|index| self.start_point.get(index)).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_end_from_id(&self, segment: SegmentId) -> Option<PointId> {
|
||||||
|
self.id_to_index(segment).and_then(|index| self.end_point.get(index)).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an array for the start and end points of a segment.
|
||||||
|
pub fn points_from_id(&self, segment: SegmentId) -> Option<[PointId; 2]> {
|
||||||
|
self.segment_start_from_id(segment).and_then(|start| self.segment_end_from_id(segment).map(|end| [start, end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to find another point in the segment that is not the one passed in.
|
||||||
|
pub fn other_point(&self, segment: SegmentId, current: PointId) -> Option<PointId> {
|
||||||
|
self.points_from_id(segment).and_then(|points| points.into_iter().find(|&point| point != current))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets all points connected to the current one but not including the current one.
|
||||||
|
pub fn connected_points(&self, current: PointId) -> impl Iterator<Item = PointId> + '_ {
|
||||||
|
self.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) {
|
||||||
|
(true, false) => Some(b),
|
||||||
|
(false, true) => Some(a),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
|
||||||
|
debug_assert_eq!(self.ids.len(), self.handles.len());
|
||||||
|
debug_assert_eq!(self.ids.len(), self.start_point.len());
|
||||||
|
debug_assert_eq!(self.ids.len(), self.end_point.len());
|
||||||
self.ids.iter().position(|&check_id| check_id == id)
|
self.ids.iter().position(|&check_id| check_id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
|
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
|
||||||
match (self.resolve_id(*range.start()), self.resolve_id(*range.end())) {
|
match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {
|
||||||
(Some(start), Some(end)) => Some(start..=end),
|
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Resolving range with invalid id");
|
warn!("Resolving range with invalid id");
|
||||||
None
|
None
|
||||||
@@ -158,6 +282,26 @@ impl SegmentDomain {
|
|||||||
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
|
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enumerate all segments that start at the point.
|
||||||
|
pub fn start_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
|
||||||
|
self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumerate all segments that end at the point.
|
||||||
|
pub fn end_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
|
||||||
|
self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
|
||||||
|
pub fn all_connected(&self, point: PointId) -> impl Iterator<Item = HandleId> + '_ {
|
||||||
|
self.start_connected(point).map(HandleId::primary).chain(self.end_connected(point).map(HandleId::end))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
|
||||||
|
pub fn connected_count(&self, point: PointId) -> usize {
|
||||||
|
self.all_connected(point).count()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]
|
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]
|
||||||
@@ -184,7 +328,19 @@ impl RegionDomain {
|
|||||||
self.fill.clear();
|
self.fill.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {
|
||||||
|
let mut keep = self.ids.iter().map(&f);
|
||||||
|
self.segment_range.retain(|_| keep.next().unwrap_or_default());
|
||||||
|
let mut keep = self.ids.iter().map(&f);
|
||||||
|
self.fill.retain(|_| keep.next().unwrap_or_default());
|
||||||
|
self.ids.retain(&f);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
|
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
|
||||||
|
if self.ids.contains(&id) {
|
||||||
|
warn!("Duplicate region");
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.ids.push(id);
|
self.ids.push(id);
|
||||||
self.segment_range.push(segment_range);
|
self.segment_range.push(segment_range);
|
||||||
self.fill.push(fill);
|
self.fill.push(fill);
|
||||||
@@ -194,6 +350,30 @@ impl RegionDomain {
|
|||||||
self.ids.iter().position(|&check_id| check_id == id)
|
self.ids.iter().position(|&check_id| check_id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next_id(&self) -> RegionId {
|
||||||
|
self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> {
|
||||||
|
self.ids.iter().copied().zip(self.segment_range.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {
|
||||||
|
self.ids.iter().copied().zip(self.fill.iter_mut())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ids(&self) -> &[RegionId] {
|
||||||
|
&self.ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] {
|
||||||
|
&self.segment_range
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(&self) -> &[FillId] {
|
||||||
|
&self.fill
|
||||||
|
}
|
||||||
|
|
||||||
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
|
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
|
||||||
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
|
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
|
||||||
self.segment_range.extend(
|
self.segment_range.extend(
|
||||||
@@ -209,15 +389,22 @@ impl RegionDomain {
|
|||||||
impl super::VectorData {
|
impl super::VectorData {
|
||||||
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. Returns [`None`] if either ID is invalid.
|
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. Returns [`None`] if either ID is invalid.
|
||||||
fn segment_to_bezier(&self, start: PointId, end: PointId, handles: bezier_rs::BezierHandles) -> Option<bezier_rs::Bezier> {
|
fn segment_to_bezier(&self, start: PointId, end: PointId, handles: bezier_rs::BezierHandles) -> Option<bezier_rs::Bezier> {
|
||||||
let start = self.point_domain.pos_from_id(start)?;
|
let start = self.point_domain.position_from_id(start)?;
|
||||||
let end = self.point_domain.pos_from_id(end)?;
|
let end = self.point_domain.position_from_id(end)?;
|
||||||
Some(bezier_rs::Bezier { start, end, handles })
|
Some(bezier_rs::Bezier { start, end, handles })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||||
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
|
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
|
||||||
let index = self.segment_domain.resolve_id(id)?;
|
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
|
||||||
self.segment_to_bezier(self.segment_domain.start_point[index], self.segment_domain.end_point[index], self.segment_domain.handles[index])
|
}
|
||||||
|
|
||||||
|
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
|
||||||
|
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
|
||||||
|
let index: usize = self.segment_domain.id_to_index(id)?;
|
||||||
|
let start = self.segment_domain.start_point[index];
|
||||||
|
let end = self.segment_domain.end_point[index];
|
||||||
|
Some((start, end, self.segment_to_bezier(start, end, self.segment_domain.handles[index])?))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
|
||||||
@@ -237,17 +424,6 @@ impl super::VectorData {
|
|||||||
let mut first_point = None;
|
let mut first_point = None;
|
||||||
let mut groups = Vec::new();
|
let mut groups = Vec::new();
|
||||||
let mut last: Option<(PointId, bezier_rs::BezierHandles)> = None;
|
let mut last: Option<(PointId, bezier_rs::BezierHandles)> = None;
|
||||||
let end_point = |last: Option<(PointId, bezier_rs::BezierHandles)>, next: Option<PointId>, groups: &mut Vec<_>| {
|
|
||||||
if let Some((disconnected_previous, previous_handle)) = last.filter(|(end, _)| !next.is_some_and(|next| next == *end)) {
|
|
||||||
groups.push(bezier_rs::ManipulatorGroup {
|
|
||||||
anchor: self.point_domain.pos_from_id(disconnected_previous)?,
|
|
||||||
in_handle: previous_handle.end(),
|
|
||||||
out_handle: None,
|
|
||||||
id: disconnected_previous,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
};
|
|
||||||
|
|
||||||
for (handle, start, end) in segments {
|
for (handle, start, end) in segments {
|
||||||
if last.is_some_and(|(previous_end, _)| previous_end != start) {
|
if last.is_some_and(|(previous_end, _)| previous_end != start) {
|
||||||
@@ -255,10 +431,9 @@ impl super::VectorData {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
first_point = Some(first_point.unwrap_or(start));
|
first_point = Some(first_point.unwrap_or(start));
|
||||||
end_point(last, Some(start), &mut groups)?;
|
|
||||||
|
|
||||||
groups.push(bezier_rs::ManipulatorGroup {
|
groups.push(bezier_rs::ManipulatorGroup {
|
||||||
anchor: self.point_domain.pos_from_id(start)?,
|
anchor: self.point_domain.position_from_id(start)?,
|
||||||
in_handle: last.and_then(|(_, handle)| handle.end()),
|
in_handle: last.and_then(|(_, handle)| handle.end()),
|
||||||
out_handle: handle.start(),
|
out_handle: handle.start(),
|
||||||
id: start,
|
id: start,
|
||||||
@@ -266,8 +441,21 @@ impl super::VectorData {
|
|||||||
|
|
||||||
last = Some((end, handle));
|
last = Some((end, handle));
|
||||||
}
|
}
|
||||||
end_point(last, None, &mut groups)?;
|
|
||||||
let closed = groups.len() > 1 && last.map(|(point, _)| point) == first_point;
|
let closed = groups.len() > 1 && last.map(|(point, _)| point) == first_point;
|
||||||
|
|
||||||
|
if let Some((end, last_handle)) = last {
|
||||||
|
if closed {
|
||||||
|
groups[0].in_handle = last_handle.end();
|
||||||
|
} else {
|
||||||
|
groups.push(bezier_rs::ManipulatorGroup {
|
||||||
|
anchor: self.point_domain.position_from_id(end)?,
|
||||||
|
in_handle: last_handle.end(),
|
||||||
|
out_handle: None,
|
||||||
|
id: end,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(bezier_rs::Subpath::new(groups, closed))
|
Some(bezier_rs::Subpath::new(groups, closed))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,10 +467,13 @@ impl super::VectorData {
|
|||||||
.zip(&self.region_domain.segment_range)
|
.zip(&self.region_domain.segment_range)
|
||||||
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
|
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
|
||||||
.filter_map(|(id, range)| {
|
.filter_map(|(id, range)| {
|
||||||
let segments_iter = self.segment_domain.handles[range.clone()]
|
let segments_iter = self
|
||||||
|
.segment_domain
|
||||||
|
.handles
|
||||||
|
.get(range.clone())?
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&self.segment_domain.start_point[range.clone()])
|
.zip(self.segment_domain.start_point.get(range.clone())?)
|
||||||
.zip(&self.segment_domain.end_point[range])
|
.zip(self.segment_domain.end_point.get(range)?)
|
||||||
.map(|((&handles, &start), &end)| (handles, start, end));
|
.map(|((&handles, &start), &end)| (handles, start, end));
|
||||||
|
|
||||||
self.subpath_from_segments(segments_iter).map(|subpath| (id, subpath))
|
self.subpath_from_segments(segments_iter).map(|subpath| (id, subpath))
|
||||||
@@ -294,6 +485,17 @@ impl super::VectorData {
|
|||||||
StrokePathIter { vector_data: self, segment_index: 0 }
|
StrokePathIter { vector_data: self, segment_index: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct an iterator [`bezier_rs::ManipulatorGroup`] for stroke.
|
||||||
|
pub fn manipulator_groups(&self) -> impl Iterator<Item = bezier_rs::ManipulatorGroup<PointId>> + '_ {
|
||||||
|
self.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get manipulator by id
|
||||||
|
pub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<bezier_rs::ManipulatorGroup<PointId>> {
|
||||||
|
let id = id.into();
|
||||||
|
self.manipulator_groups().find(|group| group.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
/// Transforms this vector data
|
/// Transforms this vector data
|
||||||
pub fn transform(&mut self, transform: DAffine2) {
|
pub fn transform(&mut self, transform: DAffine2) {
|
||||||
self.point_domain.transform(transform);
|
self.point_domain.transform(transform);
|
||||||
@@ -301,6 +503,7 @@ impl super::VectorData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct StrokePathIter<'a> {
|
pub struct StrokePathIter<'a> {
|
||||||
vector_data: &'a super::VectorData,
|
vector_data: &'a super::VectorData,
|
||||||
segment_index: usize,
|
segment_index: usize,
|
||||||
@@ -339,11 +542,6 @@ impl bezier_rs::Identifier for PointId {
|
|||||||
Self::generate()
|
Self::generate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<crate::uuid::ManipulatorGroupId> for PointId {
|
|
||||||
fn from(value: crate::uuid::ManipulatorGroupId) -> Self {
|
|
||||||
Self(value.inner())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::vector::ConcatElement for super::VectorData {
|
impl crate::vector::ConcatElement for super::VectorData {
|
||||||
fn concat(&mut self, other: &Self, transform: glam::DAffine2) {
|
fn concat(&mut self, other: &Self, transform: glam::DAffine2) {
|
||||||
@@ -369,6 +567,7 @@ impl crate::vector::ConcatElement for super::VectorData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
|
||||||
struct IdMap {
|
struct IdMap {
|
||||||
point_map: HashMap<PointId, PointId>,
|
point_map: HashMap<PointId, PointId>,
|
||||||
segment_map: HashMap<SegmentId, SegmentId>,
|
segment_map: HashMap<SegmentId, SegmentId>,
|
||||||
|
|||||||
@@ -0,0 +1,530 @@
|
|||||||
|
use super::*;
|
||||||
|
use crate::Node;
|
||||||
|
|
||||||
|
use bezier_rs::BezierHandles;
|
||||||
|
use dyn_any::{DynAny, StaticType};
|
||||||
|
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
/// Represents a procedural change to the [`PointDomain`] in [`VectorData`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct PointModification {
|
||||||
|
add: Vec<PointId>,
|
||||||
|
remove: HashSet<PointId>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
delta: HashMap<PointId, DVec2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointModification {
|
||||||
|
/// Apply this modification to the specified [`PointDomain`].
|
||||||
|
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
|
||||||
|
point_domain.retain(|id| !self.remove.contains(id));
|
||||||
|
|
||||||
|
for (id, position) in point_domain.positions_mut() {
|
||||||
|
let Some(&delta) = self.delta.get(&id) else { continue };
|
||||||
|
if !delta.is_finite() {
|
||||||
|
warn!("Invalid delta when applying a point modification");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*position += delta;
|
||||||
|
|
||||||
|
for (_, handles, start, end) in segment_domain.handles_mut() {
|
||||||
|
if start == id {
|
||||||
|
handles.move_start(delta);
|
||||||
|
}
|
||||||
|
if end == id {
|
||||||
|
handles.move_end(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for &add_id in &self.add {
|
||||||
|
let Some(&position) = self.delta.get(&add_id) else { continue };
|
||||||
|
if !position.is_finite() {
|
||||||
|
warn!("Invalid position when applying a point modification");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
point_domain.push(add_id, position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
|
||||||
|
pub fn create_from_vector(vector_data: &VectorData) -> Self {
|
||||||
|
Self {
|
||||||
|
add: vector_data.point_domain.ids().to_vec(),
|
||||||
|
remove: HashSet::new(),
|
||||||
|
delta: vector_data.point_domain.ids().iter().copied().zip(vector_data.point_domain.positions().iter().cloned()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, id: PointId, position: DVec2) {
|
||||||
|
self.add.push(id);
|
||||||
|
self.delta.insert(id, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, id: PointId) {
|
||||||
|
self.remove.insert(id);
|
||||||
|
self.add.retain(|&add| add != id);
|
||||||
|
self.delta.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a procedural change to the [`SegmentDomain`] in [`VectorData`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct SegmentModification {
|
||||||
|
add: Vec<SegmentId>,
|
||||||
|
remove: HashSet<SegmentId>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
start_point: HashMap<SegmentId, PointId>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
end_point: HashMap<SegmentId, PointId>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
handle_primary: HashMap<SegmentId, Option<DVec2>>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
handle_end: HashMap<SegmentId, Option<DVec2>>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
stroke: HashMap<SegmentId, StrokeId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SegmentModification {
|
||||||
|
/// Apply this modification to the specified [`SegmentDomain`].
|
||||||
|
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
|
||||||
|
segment_domain.retain(|id| !self.remove.contains(id));
|
||||||
|
|
||||||
|
for (id, point) in segment_domain.start_point_mut() {
|
||||||
|
let Some(&new) = self.start_point.get(&id) else { continue };
|
||||||
|
if !point_domain.ids().contains(&new) {
|
||||||
|
warn!("Invalid start ID when applying a segment modification");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*point = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, point) in segment_domain.end_point_mut() {
|
||||||
|
let Some(&new) = self.end_point.get(&id) else { continue };
|
||||||
|
if !point_domain.ids().contains(&new) {
|
||||||
|
warn!("Invalid end ID when applying a segment modification");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
*point = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, handles, start, end) in segment_domain.handles_mut() {
|
||||||
|
let Some(start) = point_domain.position_from_id(start) else { continue };
|
||||||
|
let Some(end) = point_domain.position_from_id(end) else { continue };
|
||||||
|
|
||||||
|
// Compute the actual start and end position based on the offset from the anchor
|
||||||
|
let start = self.handle_primary.get(&id).copied().map(|handle| handle.map(|handle| handle + start));
|
||||||
|
let end = self.handle_end.get(&id).copied().map(|handle| handle.map(|handle| handle + end));
|
||||||
|
|
||||||
|
if !start.unwrap_or_default().map_or(true, |start| start.is_finite()) || !end.unwrap_or_default().map_or(true, |end| end.is_finite()) {
|
||||||
|
warn!("Invalid handles when applying a segment modification");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (start, end) {
|
||||||
|
// The new handles are fully specified by the modification
|
||||||
|
(Some(Some(handle_start)), Some(Some(handle_end))) => *handles = BezierHandles::Cubic { handle_start, handle_end },
|
||||||
|
(Some(Some(handle)), Some(None)) | (Some(None), Some(Some(handle))) => *handles = BezierHandles::Quadratic { handle },
|
||||||
|
(Some(None), Some(None)) => *handles = BezierHandles::Linear,
|
||||||
|
// Remove the end handle
|
||||||
|
(None, Some(None)) => {
|
||||||
|
if let BezierHandles::Cubic { handle_start, .. } = *handles {
|
||||||
|
*handles = BezierHandles::Quadratic { handle: handle_start }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Change the end handle
|
||||||
|
(None, Some(Some(handle_end))) => match *handles {
|
||||||
|
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_end },
|
||||||
|
BezierHandles::Quadratic { handle: handle_start } => *handles = BezierHandles::Cubic { handle_start, handle_end },
|
||||||
|
BezierHandles::Cubic { handle_start, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
|
||||||
|
},
|
||||||
|
// Remove the start handle
|
||||||
|
(Some(None), None) => *handles = BezierHandles::Linear,
|
||||||
|
// Change the start handle
|
||||||
|
(Some(Some(handle_start)), None) => match *handles {
|
||||||
|
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_start },
|
||||||
|
BezierHandles::Quadratic { .. } => *handles = BezierHandles::Quadratic { handle: handle_start },
|
||||||
|
BezierHandles::Cubic { handle_end, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
|
||||||
|
},
|
||||||
|
// No change
|
||||||
|
(None, None) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, stroke) in segment_domain.stroke_mut() {
|
||||||
|
let Some(&new) = self.stroke.get(&id) else { continue };
|
||||||
|
*stroke = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &add_id in &self.add {
|
||||||
|
let Some(&start) = self.start_point.get(&add_id) else { continue };
|
||||||
|
let Some(&end) = self.end_point.get(&add_id) else { continue };
|
||||||
|
let Some(&handle_start) = self.handle_primary.get(&add_id) else { continue };
|
||||||
|
let Some(&handle_end) = self.handle_end.get(&add_id) else { continue };
|
||||||
|
let Some(&stroke) = self.stroke.get(&add_id) else { continue };
|
||||||
|
|
||||||
|
if !point_domain.ids().contains(&start) {
|
||||||
|
warn!("invalid start id");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !point_domain.ids().contains(&end) {
|
||||||
|
warn!("invalid end id");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(start_position) = point_domain.position_from_id(start) else { continue };
|
||||||
|
let Some(end_position) = point_domain.position_from_id(end) else { continue };
|
||||||
|
let handles = match (handle_start, handle_end) {
|
||||||
|
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic {
|
||||||
|
handle_start: handle_start + start_position,
|
||||||
|
handle_end: handle_end + end_position,
|
||||||
|
},
|
||||||
|
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle: handle + start_position },
|
||||||
|
(None, None) => BezierHandles::Linear,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !handles.is_finite() {
|
||||||
|
warn!("invalid handles");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment_domain.push(add_id, start, end, handles, stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
|
||||||
|
pub fn create_from_vector(vector_data: &VectorData) -> Self {
|
||||||
|
Self {
|
||||||
|
add: vector_data.segment_domain.ids().to_vec(),
|
||||||
|
remove: HashSet::new(),
|
||||||
|
start_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.start_point().iter().cloned()).collect(),
|
||||||
|
end_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.end_point().iter().cloned()).collect(),
|
||||||
|
handle_primary: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
|
||||||
|
handle_end: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
|
||||||
|
stroke: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.stroke().iter().cloned()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(&mut self, id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2], stroke: StrokeId) {
|
||||||
|
self.remove.remove(&id);
|
||||||
|
self.add.push(id);
|
||||||
|
self.start_point.insert(id, points[0]);
|
||||||
|
self.end_point.insert(id, points[1]);
|
||||||
|
self.handle_primary.insert(id, handles[0]);
|
||||||
|
self.handle_end.insert(id, handles[1]);
|
||||||
|
self.stroke.insert(id, stroke);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, id: SegmentId) {
|
||||||
|
self.remove.insert(id);
|
||||||
|
self.add.retain(|&add| add != id);
|
||||||
|
self.start_point.remove(&id);
|
||||||
|
self.end_point.remove(&id);
|
||||||
|
self.handle_primary.remove(&id);
|
||||||
|
self.handle_end.remove(&id);
|
||||||
|
self.stroke.remove(&id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a procedural change to the [`RegionDomain`] in [`VectorData`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct RegionModification {
|
||||||
|
add: Vec<RegionId>,
|
||||||
|
remove: HashSet<RegionId>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
segment_range: HashMap<RegionId, core::ops::RangeInclusive<SegmentId>>,
|
||||||
|
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
|
||||||
|
fill: HashMap<RegionId, FillId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegionModification {
|
||||||
|
/// Apply this modification to the specified [`RegionDomain`].
|
||||||
|
pub fn apply(&self, region_domain: &mut RegionDomain) {
|
||||||
|
region_domain.retain(|id| !self.remove.contains(id));
|
||||||
|
|
||||||
|
for (id, segment_range) in region_domain.segment_range_mut() {
|
||||||
|
let Some(new) = self.segment_range.get(&id) else { continue };
|
||||||
|
*segment_range = new.clone(); // Range inclusive is not copy
|
||||||
|
}
|
||||||
|
|
||||||
|
for (id, fill) in region_domain.fill_mut() {
|
||||||
|
let Some(&new) = self.fill.get(&id) else { continue };
|
||||||
|
*fill = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &add_id in &self.add {
|
||||||
|
let Some(segment_range) = self.segment_range.get(&add_id) else { continue };
|
||||||
|
let Some(&fill) = self.fill.get(&add_id) else { continue };
|
||||||
|
region_domain.push(add_id, segment_range.clone(), fill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
|
||||||
|
pub fn create_from_vector(vector_data: &VectorData) -> Self {
|
||||||
|
Self {
|
||||||
|
add: vector_data.region_domain.ids().to_vec(),
|
||||||
|
remove: HashSet::new(),
|
||||||
|
segment_range: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.segment_range().iter().cloned()).collect(),
|
||||||
|
fill: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.fill().iter().cloned()).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a procedural change to the [`VectorData`].
|
||||||
|
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub struct VectorModification {
|
||||||
|
points: PointModification,
|
||||||
|
segments: SegmentModification,
|
||||||
|
regions: RegionModification,
|
||||||
|
add_g1_continuous: HashSet<[HandleId; 2]>,
|
||||||
|
remove_g1_continuous: HashSet<[HandleId; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A modification type that can be added to a [`VectorModification`].
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum VectorModificationType {
|
||||||
|
InsertSegment { id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2] },
|
||||||
|
InsertPoint { id: PointId, position: DVec2 },
|
||||||
|
|
||||||
|
RemoveSegment { id: SegmentId },
|
||||||
|
RemovePoint { id: PointId },
|
||||||
|
|
||||||
|
SetG1Continuous { handles: [HandleId; 2], enabled: bool },
|
||||||
|
SetHandles { segment: SegmentId, handles: [Option<DVec2>; 2] },
|
||||||
|
SetPrimaryHandle { segment: SegmentId, relative_position: DVec2 },
|
||||||
|
SetEndHandle { segment: SegmentId, relative_position: DVec2 },
|
||||||
|
SetStartPoint { segment: SegmentId, id: PointId },
|
||||||
|
SetEndPoint { segment: SegmentId, id: PointId },
|
||||||
|
|
||||||
|
ApplyPointDelta { point: PointId, delta: DVec2 },
|
||||||
|
ApplyPrimaryDelta { segment: SegmentId, delta: DVec2 },
|
||||||
|
ApplyEndDelta { segment: SegmentId, delta: DVec2 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VectorModification {
|
||||||
|
/// Apply this modification to the specified [`VectorData`].
|
||||||
|
pub fn apply(&self, vector_data: &mut VectorData) {
|
||||||
|
self.points.apply(&mut vector_data.point_domain, &mut vector_data.segment_domain);
|
||||||
|
self.segments.apply(&mut vector_data.segment_domain, &vector_data.point_domain);
|
||||||
|
self.regions.apply(&mut vector_data.region_domain);
|
||||||
|
|
||||||
|
let valid = |val: &[HandleId; 2]| vector_data.segment_domain.ids().contains(&val[0].segment) && vector_data.segment_domain.ids().contains(&val[1].segment);
|
||||||
|
vector_data
|
||||||
|
.colinear_manipulators
|
||||||
|
.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));
|
||||||
|
|
||||||
|
for handles in &self.add_g1_continuous {
|
||||||
|
if !vector_data.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
|
||||||
|
vector_data.colinear_manipulators.push(*handles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a [`VectorModificationType`] to this modification.
|
||||||
|
pub fn modify(&mut self, vector_data_modification: &VectorModificationType) {
|
||||||
|
match vector_data_modification {
|
||||||
|
VectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),
|
||||||
|
VectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),
|
||||||
|
|
||||||
|
VectorModificationType::RemoveSegment { id } => self.segments.remove(*id),
|
||||||
|
VectorModificationType::RemovePoint { id } => self.points.remove(*id),
|
||||||
|
|
||||||
|
VectorModificationType::SetG1Continuous { handles, enabled } => {
|
||||||
|
if *enabled {
|
||||||
|
if !self.add_g1_continuous.contains(&[handles[1], handles[0]]) {
|
||||||
|
self.add_g1_continuous.insert(*handles);
|
||||||
|
}
|
||||||
|
self.remove_g1_continuous.remove(handles);
|
||||||
|
self.remove_g1_continuous.remove(&[handles[1], handles[0]]);
|
||||||
|
} else {
|
||||||
|
if !self.remove_g1_continuous.contains(&[handles[1], handles[0]]) {
|
||||||
|
self.remove_g1_continuous.insert(*handles);
|
||||||
|
}
|
||||||
|
self.add_g1_continuous.remove(handles);
|
||||||
|
self.add_g1_continuous.remove(&[handles[1], handles[0]]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VectorModificationType::SetHandles { segment, handles } => {
|
||||||
|
self.segments.handle_primary.insert(*segment, handles[0]);
|
||||||
|
self.segments.handle_end.insert(*segment, handles[1]);
|
||||||
|
}
|
||||||
|
VectorModificationType::SetPrimaryHandle { segment, relative_position } => {
|
||||||
|
self.segments.handle_primary.insert(*segment, Some(*relative_position));
|
||||||
|
}
|
||||||
|
VectorModificationType::SetEndHandle { segment, relative_position } => {
|
||||||
|
self.segments.handle_end.insert(*segment, Some(*relative_position));
|
||||||
|
}
|
||||||
|
VectorModificationType::SetStartPoint { segment, id } => {
|
||||||
|
self.segments.start_point.insert(*segment, *id);
|
||||||
|
}
|
||||||
|
VectorModificationType::SetEndPoint { segment, id } => {
|
||||||
|
self.segments.end_point.insert(*segment, *id);
|
||||||
|
}
|
||||||
|
|
||||||
|
VectorModificationType::ApplyPointDelta { point, delta } => {
|
||||||
|
*self.points.delta.entry(*point).or_default() += *delta;
|
||||||
|
}
|
||||||
|
VectorModificationType::ApplyPrimaryDelta { segment, delta } => {
|
||||||
|
let position = self.segments.handle_primary.entry(*segment).or_default();
|
||||||
|
*position = Some(position.unwrap_or_default() + *delta);
|
||||||
|
}
|
||||||
|
VectorModificationType::ApplyEndDelta { segment, delta } => {
|
||||||
|
let position = self.segments.handle_end.entry(*segment).or_default();
|
||||||
|
*position = Some(position.unwrap_or_default() + *delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
|
||||||
|
pub fn create_from_vector(vector_data: &VectorData) -> Self {
|
||||||
|
Self {
|
||||||
|
points: PointModification::create_from_vector(vector_data),
|
||||||
|
segments: SegmentModification::create_from_vector(vector_data),
|
||||||
|
regions: RegionModification::create_from_vector(vector_data),
|
||||||
|
add_g1_continuous: vector_data.colinear_manipulators.iter().copied().collect(),
|
||||||
|
remove_g1_continuous: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::hash::Hash for VectorModification {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
// TODO: properly implement (hashing a hashset is difficult because ordering is unstable)
|
||||||
|
PointId::generate().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node that applies a procedural modification to some [`VectorData`].
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct PathModify<VectorModificationNode> {
|
||||||
|
modification: VectorModificationNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node_fn(PathModify)]
|
||||||
|
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> VectorData {
|
||||||
|
modification.apply(&mut vector_data);
|
||||||
|
vector_data
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modify_new() {
|
||||||
|
let vector_data = VectorData::from_subpaths(
|
||||||
|
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let modify = VectorModification::create_from_vector(&vector_data);
|
||||||
|
|
||||||
|
let mut new = VectorData::empty();
|
||||||
|
modify.apply(&mut new);
|
||||||
|
assert_eq!(vector_data, new);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn modify_existing() {
|
||||||
|
use bezier_rs::{Bezier, Subpath};
|
||||||
|
let subpaths = [
|
||||||
|
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
|
||||||
|
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
|
||||||
|
Subpath::from_beziers(
|
||||||
|
&[
|
||||||
|
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
|
||||||
|
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
|
||||||
|
],
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let mut vector_data = VectorData::from_subpaths(&subpaths, false);
|
||||||
|
|
||||||
|
let mut modify_new = VectorModification::create_from_vector(&vector_data);
|
||||||
|
let mut modify_original = VectorModification::default();
|
||||||
|
|
||||||
|
for modification in [&mut modify_new, &mut modify_original] {
|
||||||
|
let point = vector_data.point_domain.ids()[0];
|
||||||
|
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });
|
||||||
|
let point = vector_data.point_domain.ids()[9];
|
||||||
|
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new = VectorData::empty();
|
||||||
|
modify_new.apply(&mut new);
|
||||||
|
|
||||||
|
modify_original.apply(&mut vector_data);
|
||||||
|
|
||||||
|
assert_eq!(vector_data, new);
|
||||||
|
assert_eq!(vector_data.point_domain.positions()[0], DVec2::X);
|
||||||
|
assert_eq!(vector_data.point_domain.positions()[9], DVec2::new(11., 0.));
|
||||||
|
assert_eq!(
|
||||||
|
vector_data.segment_bezier_iter().nth(8).unwrap().1,
|
||||||
|
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
vector_data.segment_bezier_iter().nth(9).unwrap().1,
|
||||||
|
Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
|
use serde::de::{SeqAccess, Visitor};
|
||||||
|
use serde::ser::SerializeSeq;
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use std::fmt;
|
||||||
|
use std::hash::Hash;
|
||||||
|
fn serialize_hashmap<K, V, S>(hashmap: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
K: Serialize + Eq + Hash,
|
||||||
|
V: Serialize,
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut seq = serializer.serialize_seq(Some(hashmap.len()))?;
|
||||||
|
for (key, value) in hashmap {
|
||||||
|
seq.serialize_element(&(key, value))?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_hashmap<'de, K, V, D>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
|
||||||
|
where
|
||||||
|
K: Deserialize<'de> + Eq + Hash,
|
||||||
|
V: Deserialize<'de>,
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
struct HashMapVisitor<K, V> {
|
||||||
|
marker: std::marker::PhantomData<fn() -> HashMap<K, V>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, K, V> Visitor<'de> for HashMapVisitor<K, V>
|
||||||
|
where
|
||||||
|
K: Deserialize<'de> + Eq + Hash,
|
||||||
|
V: Deserialize<'de>,
|
||||||
|
{
|
||||||
|
type Value = HashMap<K, V>;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
formatter.write_str("a sequence of tuples")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
let mut hashmap = HashMap::new();
|
||||||
|
while let Some((key, value)) = seq.next_element()? {
|
||||||
|
hashmap.insert(key, value);
|
||||||
|
}
|
||||||
|
Ok(hashmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let visitor = HashMapVisitor { marker: std::marker::PhantomData };
|
||||||
|
deserializer.deserialize_seq(visitor)
|
||||||
|
}
|
||||||
@@ -170,11 +170,11 @@ fn solidify_stroke(vector_data: VectorData) -> VectorData {
|
|||||||
// This is where we determine whether we have a closed or open path. Ex: Oval vs line segment.
|
// This is where we determine whether we have a closed or open path. Ex: Oval vs line segment.
|
||||||
if subpath_out.1.is_some() {
|
if subpath_out.1.is_some() {
|
||||||
// Two closed subpaths, closed shape. Add both subpaths.
|
// Two closed subpaths, closed shape. Add both subpaths.
|
||||||
result.append_subpath(subpath_out.0);
|
result.append_subpath(subpath_out.0, false);
|
||||||
result.append_subpath(subpath_out.1.unwrap());
|
result.append_subpath(subpath_out.1.unwrap(), false);
|
||||||
} else {
|
} else {
|
||||||
// One closed subpath, open path.
|
// One closed subpath, open path.
|
||||||
result.append_subpath(subpath_out.0);
|
result.append_subpath(subpath_out.0, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +363,7 @@ pub struct PoissonDiskPoints<SeparationDiskDiameter> {
|
|||||||
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64) -> VectorData {
|
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64) -> VectorData {
|
||||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||||
let mut result = VectorData::empty();
|
let mut result = VectorData::empty();
|
||||||
for (_, mut subpath) in vector_data.region_bezier_paths() {
|
for mut subpath in vector_data.stroke_bezier_paths() {
|
||||||
if subpath.manipulator_groups().len() < 3 {
|
if subpath.manipulator_groups().len() < 3 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -400,6 +400,8 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
|
|||||||
|
|
||||||
let first_handles = bezier_rs::solve_spline_first_handle(points.positions());
|
let first_handles = bezier_rs::solve_spline_first_handle(points.positions());
|
||||||
|
|
||||||
|
let stroke_id = StrokeId::ZERO;
|
||||||
|
|
||||||
for (start_index, end_index) in (0..(points.positions().len())).zip(1..(points.positions().len())) {
|
for (start_index, end_index) in (0..(points.positions().len())).zip(1..(points.positions().len())) {
|
||||||
let handle_start = first_handles[start_index];
|
let handle_start = first_handles[start_index];
|
||||||
let handle_end = points.positions()[end_index] * 2. - first_handles[end_index];
|
let handle_end = points.positions()[end_index] * 2. - first_handles[end_index];
|
||||||
@@ -407,7 +409,7 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
|
|||||||
|
|
||||||
vector_data
|
vector_data
|
||||||
.segment_domain
|
.segment_domain
|
||||||
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, StrokeId::generate())
|
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, stroke_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
vector_data
|
vector_data
|
||||||
@@ -484,7 +486,7 @@ async fn morph<SourceFuture: Future<Output = VectorData>, TargetFuture: Future<O
|
|||||||
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
|
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.append_subpath(source_path);
|
result.append_subpath(source_path, true);
|
||||||
}
|
}
|
||||||
// Mismatched subpath count
|
// Mismatched subpath count
|
||||||
for mut source_path in source_paths {
|
for mut source_path in source_paths {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ fn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
|
|||||||
}
|
}
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Eventually remove this (probably starting late 2024)
|
// TODO: Eventually remove this (probably starting late 2024)
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
@@ -98,7 +99,6 @@ where
|
|||||||
{
|
{
|
||||||
let input_versions = Vec::<NodeInputVersions>::deserialize(deserializer)?;
|
let input_versions = Vec::<NodeInputVersions>::deserialize(deserializer)?;
|
||||||
|
|
||||||
// Convert Vec<NodeOutput> to Vec<NodeInput>
|
|
||||||
let inputs = input_versions
|
let inputs = input_versions
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|old_input| {
|
.map(|old_input| {
|
||||||
@@ -136,8 +136,9 @@ pub struct DocumentNode {
|
|||||||
/// - From other nodes within this graph [`NodeInput::Node`],
|
/// - From other nodes within this graph [`NodeInput::Node`],
|
||||||
/// - A constant value [`NodeInput::Value`],
|
/// - A constant value [`NodeInput::Value`],
|
||||||
/// - A [`NodeInput::Network`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks.
|
/// - A [`NodeInput::Network`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks.
|
||||||
/// In the root network, it is resolved when evaluating the borrow tree.
|
///
|
||||||
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
|
/// In the root network, it is resolved when evaluating the borrow tree.
|
||||||
|
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
|
||||||
#[serde(deserialize_with = "deserialize_inputs")]
|
#[serde(deserialize_with = "deserialize_inputs")]
|
||||||
pub inputs: Vec<NodeInput>,
|
pub inputs: Vec<NodeInput>,
|
||||||
/// Manual composition is a way to override the default composition flow of one node into another.
|
/// Manual composition is a way to override the default composition flow of one node into another.
|
||||||
@@ -1214,7 +1215,7 @@ impl NodeNetwork {
|
|||||||
|
|
||||||
for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {
|
for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {
|
||||||
if let NodeInput::Network { import_index, .. } = nested_input {
|
if let NodeInput::Network { import_index, .. } = nested_input {
|
||||||
let parent_input = node.inputs.get(*import_index).expect("Import index should always exist");
|
let parent_input = node.inputs.get(*import_index).expect(&format!("Import index {} should always exist", import_index));
|
||||||
match *parent_input {
|
match *parent_input {
|
||||||
// If the input to self is a node, connect the corresponding output of the inner network to it
|
// If the input to self is a node, connect the corresponding output of the inner network to it
|
||||||
NodeInput::Node { node_id, output_index, lambda } => {
|
NodeInput::Node { node_id, output_index, lambda } => {
|
||||||
@@ -1240,7 +1241,7 @@ impl NodeNetwork {
|
|||||||
// Match the document node input and the exports of the inner network if the export is a NodeInput::Network
|
// Match the document node input and the exports of the inner network if the export is a NodeInput::Network
|
||||||
// for (i, export) in inner_network.exports.iter().enumerate() {
|
// for (i, export) in inner_network.exports.iter().enumerate() {
|
||||||
// if let NodeInput::Network { import_index, .. } = export {
|
// if let NodeInput::Network { import_index, .. } = export {
|
||||||
// let parent_input = node.inputs.get(*import_index).expect("Import index should always exist");
|
// let parent_input = node.inputs.get(*import_index).expect(&format!("Import index {} should always exist", import_index));
|
||||||
// match *parent_input {
|
// match *parent_input {
|
||||||
// // If the input to self is a node, connect the corresponding output of the inner network to it
|
// // If the input to self is a node, connect the corresponding output of the inner network to it
|
||||||
// NodeInput::Node { node_id, output_index, lambda } => {
|
// NodeInput::Node { node_id, output_index, lambda } => {
|
||||||
|
|||||||
@@ -13,11 +13,101 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
|||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
pub use std::sync::Arc;
|
pub use std::sync::Arc;
|
||||||
|
|
||||||
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
/// Macro to generate the tagged value enum.
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
macro_rules! tagged_value {
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {
|
||||||
pub enum TaggedValue {
|
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
|
||||||
None,
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
|
pub enum TaggedValue {
|
||||||
|
None,
|
||||||
|
$( $(#[$meta] ) *$identifier( $ty ), )*
|
||||||
|
RenderOutput(RenderOutput),
|
||||||
|
SurfaceFrame(graphene_core::SurfaceFrame),
|
||||||
|
}
|
||||||
|
|
||||||
|
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
|
||||||
|
#[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 => {}
|
||||||
|
$( Self::$identifier(x) => {x.hash(state)}),*
|
||||||
|
Self::RenderOutput(x) => x.hash(state),
|
||||||
|
Self::SurfaceFrame(x) => x.hash(state),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> TaggedValue {
|
||||||
|
/// Converts to a Box<dyn DynAny> - this isn't very neat but I'm not sure of a better approach
|
||||||
|
pub fn to_any(self) -> Any<'a> {
|
||||||
|
match self {
|
||||||
|
Self::None => Box::new(()),
|
||||||
|
$( Self::$identifier(x) => Box::new(x), )*
|
||||||
|
Self::RenderOutput(x) => Box::new(x),
|
||||||
|
Self::SurfaceFrame(x) => Box::new(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
|
||||||
|
pub fn ty(&self) -> Type {
|
||||||
|
match self {
|
||||||
|
Self::None => concrete!(()),
|
||||||
|
$( Self::$identifier(_) => concrete!($ty), )*
|
||||||
|
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||||
|
Self::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Attempts to downcast the dynamic type to a tagged value
|
||||||
|
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
||||||
|
use dyn_any::downcast;
|
||||||
|
use std::any::TypeId;
|
||||||
|
|
||||||
|
match DynAny::type_id(input.as_ref()) {
|
||||||
|
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
||||||
|
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
|
||||||
|
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
||||||
|
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
||||||
|
|
||||||
|
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
|
||||||
|
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
|
||||||
|
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn from_type(input: &Type) -> Self {
|
||||||
|
match input {
|
||||||
|
Type::Generic(_) => {
|
||||||
|
log::warn!("Generic type should be resolved");
|
||||||
|
TaggedValue::None
|
||||||
|
}
|
||||||
|
Type::Concrete(concrete_type) => {
|
||||||
|
let Some(internal_id) = concrete_type.id else {
|
||||||
|
return TaggedValue::None;
|
||||||
|
};
|
||||||
|
use std::any::TypeId;
|
||||||
|
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
||||||
|
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
||||||
|
match internal_id {
|
||||||
|
x if x == TypeId::of::<()>() => TaggedValue::None,
|
||||||
|
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
|
||||||
|
_ => TaggedValue::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||||
|
Type::Future(_) => {
|
||||||
|
log::warn!("Future type not used");
|
||||||
|
TaggedValue::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
tagged_value! {
|
||||||
String(String),
|
String(String),
|
||||||
U32(u32),
|
U32(u32),
|
||||||
U64(u64),
|
U64(u64),
|
||||||
@@ -33,8 +123,7 @@ pub enum TaggedValue {
|
|||||||
ImaginateCache(ImaginateCache),
|
ImaginateCache(ImaginateCache),
|
||||||
ImageFrame(graphene_core::raster::ImageFrame<Color>),
|
ImageFrame(graphene_core::raster::ImageFrame<Color>),
|
||||||
Color(graphene_core::raster::color::Color),
|
Color(graphene_core::raster::color::Color),
|
||||||
Subpaths(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
|
||||||
RcSubpath(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
|
|
||||||
BlendMode(BlendMode),
|
BlendMode(BlendMode),
|
||||||
LuminanceCalculation(LuminanceCalculation),
|
LuminanceCalculation(LuminanceCalculation),
|
||||||
ImaginateSamplingMethod(ImaginateSamplingMethod),
|
ImaginateSamplingMethod(ImaginateSamplingMethod),
|
||||||
@@ -66,7 +155,7 @@ pub enum TaggedValue {
|
|||||||
GradientStops(graphene_core::vector::style::GradientStops),
|
GradientStops(graphene_core::vector::style::GradientStops),
|
||||||
Quantization(graphene_core::quantization::QuantizationChannels),
|
Quantization(graphene_core::quantization::QuantizationChannels),
|
||||||
OptionalColor(Option<graphene_core::raster::color::Color>),
|
OptionalColor(Option<graphene_core::raster::color::Color>),
|
||||||
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
PointIds(Vec<graphene_core::vector::PointId>),
|
||||||
Font(graphene_core::text::Font),
|
Font(graphene_core::text::Font),
|
||||||
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||||
BrushCache(BrushCache),
|
BrushCache(BrushCache),
|
||||||
@@ -76,168 +165,14 @@ pub enum TaggedValue {
|
|||||||
GraphicElement(graphene_core::GraphicElement),
|
GraphicElement(graphene_core::GraphicElement),
|
||||||
ArtboardGroup(graphene_core::ArtboardGroup),
|
ArtboardGroup(graphene_core::ArtboardGroup),
|
||||||
Curve(graphene_core::raster::curve::Curve),
|
Curve(graphene_core::raster::curve::Curve),
|
||||||
SurfaceFrame(graphene_core::SurfaceFrame),
|
|
||||||
Footprint(graphene_core::transform::Footprint),
|
Footprint(graphene_core::transform::Footprint),
|
||||||
RenderOutput(RenderOutput),
|
|
||||||
Palette(Vec<Color>),
|
Palette(Vec<Color>),
|
||||||
|
VectorModification(graphene_core::vector::VectorModification),
|
||||||
CentroidType(graphene_core::vector::misc::CentroidType),
|
CentroidType(graphene_core::vector::misc::CentroidType),
|
||||||
BooleanOperation(graphene_core::vector::misc::BooleanOperation),
|
BooleanOperation(graphene_core::vector::misc::BooleanOperation),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 => {}
|
|
||||||
Self::String(x) => x.hash(state),
|
|
||||||
Self::U32(x) => x.hash(state),
|
|
||||||
Self::U64(x) => x.hash(state),
|
|
||||||
Self::F64(x) => x.to_bits().hash(state),
|
|
||||||
Self::Bool(x) => x.hash(state),
|
|
||||||
Self::UVec2(x) => x.to_array().iter().for_each(|x| x.hash(state)),
|
|
||||||
Self::IVec2(x) => x.hash(state),
|
|
||||||
Self::DVec2(x) => x.to_array().iter().for_each(|x| x.to_bits().hash(state)),
|
|
||||||
Self::OptionalDVec2(None) => 0.hash(state),
|
|
||||||
Self::OptionalDVec2(Some(x)) => {
|
|
||||||
1.hash(state);
|
|
||||||
Self::DVec2(*x).hash(state)
|
|
||||||
}
|
|
||||||
Self::DAffine2(x) => x.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)),
|
|
||||||
Self::Image(x) => x.hash(state),
|
|
||||||
Self::ImaginateCache(x) => x.hash(state),
|
|
||||||
Self::Color(x) => x.hash(state),
|
|
||||||
Self::Subpaths(x) => x.iter().for_each(|subpath| subpath.hash(state)),
|
|
||||||
Self::RcSubpath(x) => x.hash(state),
|
|
||||||
Self::BlendMode(x) => x.hash(state),
|
|
||||||
Self::LuminanceCalculation(x) => x.hash(state),
|
|
||||||
Self::ImaginateSamplingMethod(x) => x.hash(state),
|
|
||||||
Self::ImaginateMaskStartingFill(x) => x.hash(state),
|
|
||||||
Self::ImaginateController(x) => x.hash(state),
|
|
||||||
Self::ImageFrame(x) => x.hash(state),
|
|
||||||
Self::VectorData(x) => x.hash(state),
|
|
||||||
Self::Fill(x) => x.hash(state),
|
|
||||||
Self::Stroke(x) => x.hash(state),
|
|
||||||
Self::F64Array4(x) => x.iter().for_each(|x| x.to_bits().hash(state)),
|
|
||||||
Self::VecF64(x) => x.iter().for_each(|val| val.to_bits().hash(state)),
|
|
||||||
Self::VecDVec2(x) => x.iter().for_each(|val| val.to_array().iter().for_each(|x| x.to_bits().hash(state))),
|
|
||||||
Self::RedGreenBlue(x) => x.hash(state),
|
|
||||||
Self::RedGreenBlueAlpha(x) => x.hash(state),
|
|
||||||
Self::NoiseType(x) => x.hash(state),
|
|
||||||
Self::FractalType(x) => x.hash(state),
|
|
||||||
Self::CellularDistanceFunction(x) => x.hash(state),
|
|
||||||
Self::CellularReturnType(x) => x.hash(state),
|
|
||||||
Self::DomainWarpType(x) => x.hash(state),
|
|
||||||
Self::RelativeAbsolute(x) => x.hash(state),
|
|
||||||
Self::SelectiveColorChoice(x) => x.hash(state),
|
|
||||||
Self::LineCap(x) => x.hash(state),
|
|
||||||
Self::LineJoin(x) => x.hash(state),
|
|
||||||
Self::FillType(x) => x.hash(state),
|
|
||||||
Self::FillChoice(x) => x.hash(state),
|
|
||||||
Self::Gradient(x) => x.hash(state),
|
|
||||||
Self::GradientType(x) => x.hash(state),
|
|
||||||
Self::GradientStops(x) => {
|
|
||||||
x.0.len().hash(state);
|
|
||||||
for (position, color) in &x.0 {
|
|
||||||
position.to_bits().hash(state);
|
|
||||||
color.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Quantization(x) => x.hash(state),
|
|
||||||
Self::OptionalColor(x) => x.hash(state),
|
|
||||||
Self::ManipulatorGroupIds(x) => x.hash(state),
|
|
||||||
Self::Font(x) => x.hash(state),
|
|
||||||
Self::BrushStrokes(x) => x.hash(state),
|
|
||||||
Self::BrushCache(x) => x.hash(state),
|
|
||||||
Self::Segments(x) => {
|
|
||||||
for segment in x {
|
|
||||||
segment.hash(state)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::DocumentNode(x) => x.hash(state),
|
|
||||||
Self::GraphicGroup(x) => x.hash(state),
|
|
||||||
Self::GraphicElement(x) => x.hash(state),
|
|
||||||
Self::ArtboardGroup(x) => x.hash(state),
|
|
||||||
Self::Curve(x) => x.hash(state),
|
|
||||||
Self::SurfaceFrame(x) => x.hash(state),
|
|
||||||
Self::Footprint(x) => x.hash(state),
|
|
||||||
Self::RenderOutput(x) => x.hash(state),
|
|
||||||
Self::Palette(x) => x.hash(state),
|
|
||||||
Self::CentroidType(x) => x.hash(state),
|
|
||||||
Self::BooleanOperation(x) => x.hash(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TaggedValue {
|
impl<'a> TaggedValue {
|
||||||
/// Converts to a Box<dyn DynAny> - this isn't very neat but I'm not sure of a better approach
|
|
||||||
pub fn to_any(self) -> Any<'a> {
|
|
||||||
match self {
|
|
||||||
TaggedValue::None => Box::new(()),
|
|
||||||
TaggedValue::String(x) => Box::new(x),
|
|
||||||
TaggedValue::U32(x) => Box::new(x),
|
|
||||||
TaggedValue::U64(x) => Box::new(x),
|
|
||||||
TaggedValue::F64(x) => Box::new(x),
|
|
||||||
TaggedValue::Bool(x) => Box::new(x),
|
|
||||||
TaggedValue::UVec2(x) => Box::new(x),
|
|
||||||
TaggedValue::IVec2(x) => Box::new(x),
|
|
||||||
TaggedValue::DVec2(x) => Box::new(x),
|
|
||||||
TaggedValue::OptionalDVec2(x) => Box::new(x),
|
|
||||||
TaggedValue::DAffine2(x) => Box::new(x),
|
|
||||||
TaggedValue::Image(x) => Box::new(x),
|
|
||||||
TaggedValue::ImaginateCache(x) => Box::new(x),
|
|
||||||
TaggedValue::ImageFrame(x) => Box::new(x),
|
|
||||||
TaggedValue::Color(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),
|
|
||||||
TaggedValue::ImaginateSamplingMethod(x) => Box::new(x),
|
|
||||||
TaggedValue::ImaginateMaskStartingFill(x) => Box::new(x),
|
|
||||||
TaggedValue::ImaginateController(x) => Box::new(x),
|
|
||||||
TaggedValue::VectorData(x) => Box::new(x),
|
|
||||||
TaggedValue::Fill(x) => Box::new(x),
|
|
||||||
TaggedValue::Stroke(x) => Box::new(x),
|
|
||||||
TaggedValue::F64Array4(x) => Box::new(x),
|
|
||||||
TaggedValue::VecF64(x) => Box::new(x),
|
|
||||||
TaggedValue::VecDVec2(x) => Box::new(x),
|
|
||||||
TaggedValue::RedGreenBlue(x) => Box::new(x),
|
|
||||||
TaggedValue::RedGreenBlueAlpha(x) => Box::new(x),
|
|
||||||
TaggedValue::NoiseType(x) => Box::new(x),
|
|
||||||
TaggedValue::FractalType(x) => Box::new(x),
|
|
||||||
TaggedValue::CellularDistanceFunction(x) => Box::new(x),
|
|
||||||
TaggedValue::CellularReturnType(x) => Box::new(x),
|
|
||||||
TaggedValue::DomainWarpType(x) => Box::new(x),
|
|
||||||
TaggedValue::RelativeAbsolute(x) => Box::new(x),
|
|
||||||
TaggedValue::SelectiveColorChoice(x) => Box::new(x),
|
|
||||||
TaggedValue::LineCap(x) => Box::new(x),
|
|
||||||
TaggedValue::LineJoin(x) => Box::new(x),
|
|
||||||
TaggedValue::FillType(x) => Box::new(x),
|
|
||||||
TaggedValue::FillChoice(x) => Box::new(x),
|
|
||||||
TaggedValue::Gradient(x) => Box::new(x),
|
|
||||||
TaggedValue::GradientType(x) => Box::new(x),
|
|
||||||
TaggedValue::GradientStops(x) => Box::new(x),
|
|
||||||
TaggedValue::Quantization(x) => Box::new(x),
|
|
||||||
TaggedValue::OptionalColor(x) => Box::new(x),
|
|
||||||
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
|
|
||||||
TaggedValue::Font(x) => Box::new(x),
|
|
||||||
TaggedValue::BrushStrokes(x) => Box::new(x),
|
|
||||||
TaggedValue::BrushCache(x) => Box::new(x),
|
|
||||||
TaggedValue::Segments(x) => Box::new(x),
|
|
||||||
TaggedValue::DocumentNode(x) => Box::new(x),
|
|
||||||
TaggedValue::GraphicGroup(x) => Box::new(x),
|
|
||||||
TaggedValue::GraphicElement(x) => Box::new(x),
|
|
||||||
TaggedValue::ArtboardGroup(x) => Box::new(x),
|
|
||||||
TaggedValue::Curve(x) => Box::new(x),
|
|
||||||
TaggedValue::SurfaceFrame(x) => Box::new(x),
|
|
||||||
TaggedValue::Footprint(x) => Box::new(x),
|
|
||||||
TaggedValue::RenderOutput(x) => Box::new(x),
|
|
||||||
TaggedValue::Palette(x) => Box::new(x),
|
|
||||||
TaggedValue::CentroidType(x) => Box::new(x),
|
|
||||||
TaggedValue::BooleanOperation(x) => Box::new(x),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
TaggedValue::String(x) => x.to_string(),
|
TaggedValue::String(x) => x.to_string(),
|
||||||
@@ -248,7 +183,6 @@ impl<'a> TaggedValue {
|
|||||||
_ => panic!("Cannot convert to string"),
|
_ => panic!("Cannot convert to string"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_primitive_string(&self) -> String {
|
pub fn to_primitive_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
TaggedValue::None => "()".to_string(),
|
TaggedValue::None => "()".to_string(),
|
||||||
@@ -262,233 +196,6 @@ impl<'a> TaggedValue {
|
|||||||
_ => panic!("Cannot convert to primitive string"),
|
_ => panic!("Cannot convert to primitive string"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ty(&self) -> Type {
|
|
||||||
match self {
|
|
||||||
TaggedValue::None => concrete!(()),
|
|
||||||
TaggedValue::String(_) => concrete!(String),
|
|
||||||
TaggedValue::U32(_) => concrete!(u32),
|
|
||||||
TaggedValue::U64(_) => concrete!(u64),
|
|
||||||
TaggedValue::F64(_) => concrete!(f64),
|
|
||||||
TaggedValue::Bool(_) => concrete!(bool),
|
|
||||||
TaggedValue::UVec2(_) => concrete!(UVec2),
|
|
||||||
TaggedValue::IVec2(_) => concrete!(IVec2),
|
|
||||||
TaggedValue::DVec2(_) => concrete!(DVec2),
|
|
||||||
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
|
|
||||||
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image<Color>),
|
|
||||||
TaggedValue::ImaginateCache(_) => concrete!(ImaginateCache),
|
|
||||||
TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame<Color>),
|
|
||||||
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
|
|
||||||
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),
|
|
||||||
TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill),
|
|
||||||
TaggedValue::ImaginateController(_) => concrete!(ImaginateController),
|
|
||||||
TaggedValue::DAffine2(_) => concrete!(DAffine2),
|
|
||||||
TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation),
|
|
||||||
TaggedValue::VectorData(_) => concrete!(graphene_core::vector::VectorData),
|
|
||||||
TaggedValue::Fill(_) => concrete!(graphene_core::vector::style::Fill),
|
|
||||||
TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke),
|
|
||||||
TaggedValue::F64Array4(_) => concrete!([f64; 4]),
|
|
||||||
TaggedValue::VecF64(_) => concrete!(Vec<f64>),
|
|
||||||
TaggedValue::VecDVec2(_) => concrete!(Vec<DVec2>),
|
|
||||||
TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue),
|
|
||||||
TaggedValue::RedGreenBlueAlpha(_) => concrete!(graphene_core::raster::RedGreenBlueAlpha),
|
|
||||||
TaggedValue::NoiseType(_) => concrete!(graphene_core::raster::NoiseType),
|
|
||||||
TaggedValue::FractalType(_) => concrete!(graphene_core::raster::FractalType),
|
|
||||||
TaggedValue::CellularDistanceFunction(_) => concrete!(graphene_core::raster::CellularDistanceFunction),
|
|
||||||
TaggedValue::CellularReturnType(_) => concrete!(graphene_core::raster::CellularReturnType),
|
|
||||||
TaggedValue::DomainWarpType(_) => concrete!(graphene_core::raster::DomainWarpType),
|
|
||||||
TaggedValue::RelativeAbsolute(_) => concrete!(graphene_core::raster::RelativeAbsolute),
|
|
||||||
TaggedValue::SelectiveColorChoice(_) => concrete!(graphene_core::raster::SelectiveColorChoice),
|
|
||||||
TaggedValue::LineCap(_) => concrete!(graphene_core::vector::style::LineCap),
|
|
||||||
TaggedValue::LineJoin(_) => concrete!(graphene_core::vector::style::LineJoin),
|
|
||||||
TaggedValue::FillType(_) => concrete!(graphene_core::vector::style::FillType),
|
|
||||||
TaggedValue::FillChoice(_) => concrete!(graphene_core::vector::style::FillChoice),
|
|
||||||
TaggedValue::Gradient(_) => concrete!(graphene_core::vector::style::Gradient),
|
|
||||||
TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType),
|
|
||||||
TaggedValue::GradientStops(_) => concrete!(graphene_core::vector::style::GradientStops),
|
|
||||||
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
|
|
||||||
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
|
|
||||||
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
|
|
||||||
TaggedValue::Font(_) => concrete!(graphene_core::text::Font),
|
|
||||||
TaggedValue::BrushStrokes(_) => concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
|
||||||
TaggedValue::BrushCache(_) => concrete!(BrushCache),
|
|
||||||
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
|
|
||||||
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
|
|
||||||
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
|
|
||||||
TaggedValue::GraphicElement(_) => concrete!(graphene_core::GraphicElement),
|
|
||||||
TaggedValue::ArtboardGroup(_) => concrete!(graphene_core::ArtboardGroup),
|
|
||||||
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
|
|
||||||
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
|
|
||||||
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
|
|
||||||
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
|
|
||||||
TaggedValue::Palette(_) => concrete!(Vec<Color>),
|
|
||||||
TaggedValue::CentroidType(_) => concrete!(graphene_core::vector::misc::CentroidType),
|
|
||||||
TaggedValue::BooleanOperation(_) => concrete!(graphene_core::vector::misc::BooleanOperation),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
|
|
||||||
use dyn_any::downcast;
|
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
match DynAny::type_id(input.as_ref()) {
|
|
||||||
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
|
|
||||||
x if x == TypeId::of::<String>() => Ok(TaggedValue::String(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<u32>() => Ok(TaggedValue::U32(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<u64>() => Ok(TaggedValue::U64(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<f64>() => Ok(TaggedValue::F64(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<bool>() => Ok(TaggedValue::Bool(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<UVec2>() => Ok(TaggedValue::UVec2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<DVec2>() => Ok(TaggedValue::DVec2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Option<DVec2>>() => Ok(TaggedValue::OptionalDVec2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => Ok(TaggedValue::Image(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<ImaginateCache>() => Ok(TaggedValue::ImaginateCache(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::ImageFrame<Color>>() => Ok(TaggedValue::ImageFrame(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::Color>() => Ok(TaggedValue::Color(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => Ok(TaggedValue::Subpaths(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => Ok(TaggedValue::RcSubpath(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<BlendMode>() => Ok(TaggedValue::BlendMode(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<ImaginateSamplingMethod>() => Ok(TaggedValue::ImaginateSamplingMethod(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<ImaginateMaskStartingFill>() => Ok(TaggedValue::ImaginateMaskStartingFill(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<ImaginateController>() => Ok(TaggedValue::ImaginateController(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<DAffine2>() => Ok(TaggedValue::DAffine2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<LuminanceCalculation>() => Ok(TaggedValue::LuminanceCalculation(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::VectorData>() => Ok(TaggedValue::VectorData(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::Fill>() => Ok(TaggedValue::Fill(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::Stroke>() => Ok(TaggedValue::Stroke(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<f64>>() => Ok(TaggedValue::VecF64(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<DVec2>>() => Ok(TaggedValue::VecDVec2(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => Ok(TaggedValue::RedGreenBlue(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RedGreenBlueAlpha>() => Ok(TaggedValue::RedGreenBlueAlpha(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => Ok(TaggedValue::NoiseType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::FractalType>() => Ok(TaggedValue::FractalType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::CellularDistanceFunction>() => Ok(TaggedValue::CellularDistanceFunction(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::CellularReturnType>() => Ok(TaggedValue::CellularReturnType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::DomainWarpType>() => Ok(TaggedValue::DomainWarpType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => Ok(TaggedValue::RelativeAbsolute(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => Ok(TaggedValue::SelectiveColorChoice(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => Ok(TaggedValue::LineCap(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => Ok(TaggedValue::LineJoin(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => Ok(TaggedValue::FillType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::FillChoice>() => Ok(TaggedValue::FillChoice(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::Gradient>() => Ok(TaggedValue::Gradient(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => Ok(TaggedValue::GradientType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => Ok(TaggedValue::GradientStops(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => Ok(TaggedValue::Quantization(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Option<graphene_core::Color>>() => Ok(TaggedValue::OptionalColor(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => Ok(TaggedValue::ManipulatorGroupIds(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::text::Font>() => Ok(TaggedValue::Font(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<BrushCache>() => Ok(TaggedValue::BrushCache(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Ok(TaggedValue::Segments(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<crate::document::DocumentNode>() => Ok(TaggedValue::DocumentNode(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Ok(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::GraphicElement>() => Ok(TaggedValue::GraphicElement(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => Ok(TaggedValue::ArtboardGroup(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
|
|
||||||
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
|
|
||||||
Ok(TaggedValue::SurfaceFrame(frame.into()))
|
|
||||||
}
|
|
||||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<Vec<Color>>() => Ok(TaggedValue::Palette(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => Ok(TaggedValue::CentroidType(*downcast(input).unwrap())),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::misc::BooleanOperation>() => Ok(TaggedValue::BooleanOperation(*downcast(input).unwrap())),
|
|
||||||
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn from_type(input: &Type) -> Self {
|
|
||||||
match input {
|
|
||||||
Type::Generic(_) => {
|
|
||||||
log::warn!("Generic type should be resolved");
|
|
||||||
TaggedValue::None
|
|
||||||
}
|
|
||||||
Type::Concrete(concrete_type) => {
|
|
||||||
let Some(internal_id) = concrete_type.id else {
|
|
||||||
return TaggedValue::None;
|
|
||||||
};
|
|
||||||
use std::any::TypeId;
|
|
||||||
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
|
|
||||||
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
|
|
||||||
match internal_id {
|
|
||||||
x if x == TypeId::of::<()>() => TaggedValue::None,
|
|
||||||
x if x == TypeId::of::<String>() => TaggedValue::String(Default::default()),
|
|
||||||
x if x == TypeId::of::<u32>() => TaggedValue::U32(Default::default()),
|
|
||||||
x if x == TypeId::of::<u64>() => TaggedValue::U64(Default::default()),
|
|
||||||
x if x == TypeId::of::<f64>() => TaggedValue::F64(Default::default()),
|
|
||||||
x if x == TypeId::of::<bool>() => TaggedValue::Bool(Default::default()),
|
|
||||||
x if x == TypeId::of::<UVec2>() => TaggedValue::UVec2(Default::default()),
|
|
||||||
x if x == TypeId::of::<IVec2>() => TaggedValue::IVec2(Default::default()),
|
|
||||||
x if x == TypeId::of::<DVec2>() => TaggedValue::DVec2(Default::default()),
|
|
||||||
x if x == TypeId::of::<Option<DVec2>>() => TaggedValue::OptionalDVec2(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => TaggedValue::Image(Default::default()),
|
|
||||||
x if x == TypeId::of::<ImaginateCache>() => TaggedValue::ImaginateCache(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::ImageFrame<Color>>() => TaggedValue::ImageFrame(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::Color>() => TaggedValue::Color(Default::default()),
|
|
||||||
x if x == TypeId::of::<Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::Subpaths(vec![]),
|
|
||||||
x if x == TypeId::of::<Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::None,
|
|
||||||
x if x == TypeId::of::<BlendMode>() => TaggedValue::BlendMode(Default::default()),
|
|
||||||
x if x == TypeId::of::<ImaginateSamplingMethod>() => TaggedValue::ImaginateSamplingMethod(Default::default()),
|
|
||||||
x if x == TypeId::of::<ImaginateMaskStartingFill>() => TaggedValue::ImaginateMaskStartingFill(Default::default()),
|
|
||||||
x if x == TypeId::of::<ImaginateController>() => TaggedValue::ImaginateController(Default::default()),
|
|
||||||
x if x == TypeId::of::<DAffine2>() => TaggedValue::DAffine2(Default::default()),
|
|
||||||
x if x == TypeId::of::<LuminanceCalculation>() => TaggedValue::LuminanceCalculation(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::VectorData>() => TaggedValue::VectorData(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::Fill>() => TaggedValue::Fill(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::Stroke>() => TaggedValue::Stroke(Default::default()),
|
|
||||||
x if x == TypeId::of::<Vec<f64>>() => TaggedValue::VecF64(Default::default()),
|
|
||||||
x if x == TypeId::of::<Vec<DVec2>>() => TaggedValue::VecDVec2(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => TaggedValue::RedGreenBlue(graphene_core::raster::RedGreenBlue::Red),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RedGreenBlueAlpha>() => TaggedValue::RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha::Red),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => TaggedValue::NoiseType(graphene_core::raster::NoiseType::Perlin),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::FractalType>() => TaggedValue::FractalType(graphene_core::raster::FractalType::None),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::CellularDistanceFunction>() => TaggedValue::CellularDistanceFunction(graphene_core::raster::CellularDistanceFunction::Euclidean),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::CellularReturnType>() => TaggedValue::CellularReturnType(graphene_core::raster::CellularReturnType::Nearest),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::DomainWarpType>() => TaggedValue::DomainWarpType(graphene_core::raster::DomainWarpType::None),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => TaggedValue::RelativeAbsolute(graphene_core::raster::RelativeAbsolute::Relative),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => TaggedValue::SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice::Reds),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => TaggedValue::FillType(graphene_core::vector::style::FillType::Solid),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => TaggedValue::GradientType(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => TaggedValue::GradientStops(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => TaggedValue::Quantization(Default::default()),
|
|
||||||
x if x == TypeId::of::<Option<graphene_core::Color>>() => TaggedValue::OptionalColor(Default::default()),
|
|
||||||
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => TaggedValue::ManipulatorGroupIds(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::text::Font>() => TaggedValue::Font(graphene_core::text::Font::new(
|
|
||||||
graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
|
|
||||||
graphene_core::consts::DEFAULT_FONT_STYLE.into(),
|
|
||||||
)),
|
|
||||||
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => TaggedValue::BrushStrokes(Default::default()),
|
|
||||||
x if x == TypeId::of::<BrushCache>() => TaggedValue::BrushCache(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => TaggedValue::Segments(Default::default()),
|
|
||||||
x if x == TypeId::of::<crate::document::DocumentNode>() => TaggedValue::DocumentNode(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::GraphicGroup>() => TaggedValue::GraphicGroup(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::GraphicElement>() => TaggedValue::GraphicElement(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::Artboard>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
|
|
||||||
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
|
|
||||||
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => TaggedValue::None,
|
|
||||||
x if x == TypeId::of::<RenderOutput>() => TaggedValue::None,
|
|
||||||
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => TaggedValue::None,
|
|
||||||
x if x == TypeId::of::<graphene_core::transform::Footprint>() => TaggedValue::Footprint(Default::default()),
|
|
||||||
x if x == TypeId::of::<Vec<Color>>() => TaggedValue::Palette(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => TaggedValue::CentroidType(Default::default()),
|
|
||||||
x if x == TypeId::of::<graphene_core::vector::misc::BooleanOperation>() => TaggedValue::BooleanOperation(Default::default()),
|
|
||||||
_ => TaggedValue::None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
|
||||||
Type::Future(_) => {
|
|
||||||
log::warn!("Future type not used");
|
|
||||||
TaggedValue::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UpcastNode {
|
pub struct UpcastNode {
|
||||||
@@ -514,3 +221,53 @@ pub enum RenderOutput {
|
|||||||
Svg(String),
|
Svg(String),
|
||||||
Image(Vec<u8>),
|
Image(Vec<u8>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
|
||||||
|
trait FakeHash {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
|
||||||
|
}
|
||||||
|
mod fake_hash {
|
||||||
|
use super::*;
|
||||||
|
impl FakeHash for f64 {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.to_bits().hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FakeHash for DVec2 {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.to_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FakeHash for DAffine2 {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<X: FakeHash> FakeHash for Option<X> {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
if let Some(x) = self {
|
||||||
|
1.hash(state);
|
||||||
|
x.hash(state);
|
||||||
|
} else {
|
||||||
|
0.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<X: FakeHash> FakeHash for Vec<X> {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.len().hash(state);
|
||||||
|
self.iter().for_each(|x| x.hash(state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T: FakeHash, const N: usize> FakeHash for [T; N] {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.iter().for_each(|x| x.hash(state))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FakeHash for (f64, Color) {
|
||||||
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.0.to_bits().hash(state);
|
||||||
|
self.1.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ pub struct Compiler {}
|
|||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub fn compile(&self, mut network: NodeNetwork) -> Result<impl Iterator<Item = ProtoNetwork>, String> {
|
pub fn compile(&self, mut network: NodeNetwork) -> Result<impl Iterator<Item = ProtoNetwork>, String> {
|
||||||
println!("flattening");
|
|
||||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||||
for id in node_ids {
|
for id in node_ids {
|
||||||
network.flatten(id);
|
network.flatten(id);
|
||||||
|
|||||||
@@ -605,7 +605,8 @@ impl core::fmt::Debug for GraphErrorType {
|
|||||||
"Node graph type error! If this just appeared while editing the graph,\n\
|
"Node graph type error! If this just appeared while editing the graph,\n\
|
||||||
consider using undo to go back and try another way to connect the nodes.\n\
|
consider using undo to go back and try another way to connect the nodes.\n\
|
||||||
\n\
|
\n\
|
||||||
No node implementation exists for type ({parameters}).\n\
|
No node implementation exists for type:\n\
|
||||||
|
({parameters})\n\
|
||||||
\n\
|
\n\
|
||||||
Caused by{}:\n\
|
Caused by{}:\n\
|
||||||
{}",
|
{}",
|
||||||
@@ -788,7 +789,6 @@ impl TypingContext {
|
|||||||
|
|
||||||
match valid_impls.as_slice() {
|
match valid_impls.as_slice() {
|
||||||
[] => {
|
[] => {
|
||||||
dbg!(&self.inferred);
|
|
||||||
let mut best_errors = usize::MAX;
|
let mut best_errors = usize::MAX;
|
||||||
let mut error_inputs = Vec::new();
|
let mut error_inputs = Vec::new();
|
||||||
for node_io in impls.keys() {
|
for node_io in impls.keys() {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::Node;
|
|||||||
|
|
||||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||||
use graphene_core::transform::Footprint;
|
use graphene_core::transform::Footprint;
|
||||||
use graphene_core::uuid::ManipulatorGroupId;
|
|
||||||
use graphene_core::vector::misc::BooleanOperation;
|
use graphene_core::vector::misc::BooleanOperation;
|
||||||
pub use graphene_core::vector::*;
|
pub use graphene_core::vector::*;
|
||||||
|
|
||||||
@@ -69,10 +68,10 @@ fn from_svg_string(svg_string: &str) -> VectorData {
|
|||||||
return VectorData::empty();
|
return VectorData::empty();
|
||||||
};
|
};
|
||||||
|
|
||||||
VectorData::from_subpaths(convert_usvg_path(path))
|
VectorData::from_subpaths(convert_usvg_path(path), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<ManipulatorGroupId>> {
|
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
|
||||||
let mut subpaths = Vec::new();
|
let mut subpaths = Vec::new();
|
||||||
let mut groups = Vec::new();
|
let mut groups = Vec::new();
|
||||||
|
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ pub struct DrawImageFrameNode<Surface> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node_fn(DrawImageFrameNode)]
|
#[node_macro::node_fn(DrawImageFrameNode)]
|
||||||
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> SurfaceHandleFrame<HtmlCanvasElement> {
|
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> SurfaceHandleFrame<HtmlCanvasElement> {
|
||||||
let image_data = image.image.data;
|
let image_data = image.image.data;
|
||||||
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
|
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
|
||||||
if image.image.width > 0 && image.image.height > 0 {
|
if image.image.width > 0 && image.image.height > 0 {
|
||||||
@@ -290,9 +290,9 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
|
|||||||
pub use graph_craft::document::value::RenderOutput;
|
pub use graph_craft::document::value::RenderOutput;
|
||||||
pub struct RenderNode<Data, Surface, Parameter> {
|
pub struct RenderNode<Data, Surface, Parameter> {
|
||||||
data: Data,
|
data: Data,
|
||||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
|
||||||
surface_handle: Surface,
|
surface_handle: Surface,
|
||||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
|
||||||
surface_handle: PhantomData<Surface>,
|
surface_handle: PhantomData<Surface>,
|
||||||
parameter: PhantomData<Parameter>,
|
parameter: PhantomData<Parameter>,
|
||||||
}
|
}
|
||||||
@@ -426,7 +426,7 @@ impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<
|
|||||||
where
|
where
|
||||||
Data: Node<'input, Footprint, Output = F>,
|
Data: Node<'input, Footprint, Output = F>,
|
||||||
Surface: Node<'input, (), Output = SurfaceFuture>,
|
Surface: Node<'input, (), Output = SurfaceFuture>,
|
||||||
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
|
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
|
||||||
{
|
{
|
||||||
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
|
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
|
||||||
|
|
||||||
@@ -441,7 +441,7 @@ where
|
|||||||
let output_format = editor.render_config.export_format;
|
let output_format = editor.render_config.export_format;
|
||||||
match output_format {
|
match output_format {
|
||||||
ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint),
|
ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint),
|
||||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
|
||||||
ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
|
ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
|
||||||
_ => todo!("Non-SVG render output for {output_format:?}"),
|
_ => todo!("Non-SVG render output for {output_format:?}"),
|
||||||
}
|
}
|
||||||
@@ -455,7 +455,7 @@ impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<
|
|||||||
where
|
where
|
||||||
Data: Node<'input, (), Output = F>,
|
Data: Node<'input, (), Output = F>,
|
||||||
Surface: Node<'input, (), Output = SurfaceFuture>,
|
Surface: Node<'input, (), Output = SurfaceFuture>,
|
||||||
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
|
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
|
||||||
{
|
{
|
||||||
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
|
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -469,7 +469,7 @@ where
|
|||||||
let output_format = editor.render_config.export_format;
|
let output_format = editor.render_config.export_format;
|
||||||
match output_format {
|
match output_format {
|
||||||
ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint),
|
ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint),
|
||||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
|
||||||
ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
|
ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
|
||||||
_ => todo!("Non-SVG render output for {output_format:?}"),
|
_ => todo!("Non-SVG render output for {output_format:?}"),
|
||||||
}
|
}
|
||||||
@@ -481,9 +481,9 @@ impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
|
|||||||
pub fn new(data: Data, surface_handle: Surface) -> Self {
|
pub fn new(data: Data, surface_handle: Surface) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data,
|
data,
|
||||||
#[cfg(any(feature = "resvg", feature = "vello"))]
|
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
|
||||||
surface_handle,
|
surface_handle,
|
||||||
#[cfg(not(any(feature = "resvg", feature = "vello")))]
|
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
|
||||||
surface_handle: PhantomData,
|
surface_handle: PhantomData,
|
||||||
parameter: PhantomData,
|
parameter: PhantomData,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicGroup, fn_params: [Footprint => graphene_core::GraphicGroup]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicGroup, fn_params: [Footprint => graphene_core::GraphicGroup]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicElement, fn_params: [Footprint => graphene_core::GraphicElement]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicElement, fn_params: [Footprint => graphene_core::GraphicElement]),
|
||||||
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => graphene_core::Artboard]),
|
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => Artboard]),
|
||||||
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
|
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
|
||||||
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
|
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
|
||||||
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
|
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
|
||||||
@@ -522,7 +522,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f32), fn_type!(f32), fn_type!(bool)]),
|
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
|
||||||
)],
|
)],
|
||||||
vec![
|
vec![
|
||||||
(
|
(
|
||||||
@@ -666,7 +666,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec<DVec2>, params: [Vec<DVec2>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec<DVec2>, params: [Vec<DVec2>]),
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<WasmSurfaceHandle>, params: [Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: [Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame<WgpuExecutor>, params: [ShaderInputFrame<WgpuExecutor>]),
|
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame<WgpuExecutor>, params: [ShaderInputFrame<WgpuExecutor>]),
|
||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
@@ -683,23 +683,23 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
|
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
|
||||||
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
|
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
|
||||||
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
|
||||||
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
|
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
|
||||||
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
|
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
|
||||||
@@ -800,9 +800,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
|
|||||||
register_node!(graphene_core::vector::generator_nodes::SplineGenerator<_>, input: (), params: [Vec<DVec2>]),
|
register_node!(graphene_core::vector::generator_nodes::SplineGenerator<_>, input: (), params: [Vec<DVec2>]),
|
||||||
register_node!(
|
register_node!(
|
||||||
graphene_core::vector::generator_nodes::PathGenerator<_>,
|
graphene_core::vector::generator_nodes::PathGenerator<_>,
|
||||||
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
|
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::vector::PointId>>,
|
||||||
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
|
params: [Vec<graphene_core::vector::PointId>]
|
||||||
),
|
),
|
||||||
|
register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]),
|
||||||
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
|
||||||
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
|
||||||
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
|
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ use syn::{
|
|||||||
///
|
///
|
||||||
/// - Mapping the type of the function's first argument (the node's primary input) to the impl'd `Node`'s generic type, e.g.:
|
/// - Mapping the type of the function's first argument (the node's primary input) to the impl'd `Node`'s generic type, e.g.:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// Node<'input, Color>
|
/// Node<'input, Color>
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// for a `Color` primary input type.
|
/// for a `Color` primary input type.
|
||||||
/// - Mapping the type of the function's remaining arguments (the node's secondary inputs) to the given struct fields' generic types, e.g.:
|
/// - Mapping the type of the function's remaining arguments (the node's secondary inputs) to the given struct fields' generic types, e.g.:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// TheGivenStruct<S0, S1>
|
/// TheGivenStruct<S0, S1>
|
||||||
/// where S0: Node<'input, (), Output = f64>,
|
/// where S0: Node<'input, (), Output = f64>,
|
||||||
/// where S1: Node<'input, (), Output = f64>,
|
/// where S1: Node<'input, (), Output = f64>,
|
||||||
@@ -49,7 +49,7 @@ use syn::{
|
|||||||
/// for two `f64` parameter (secondary input) types. Since Graphene works by having each function evaluate its upstream node as a lambda that returns output data, these secondary inputs are not directly `f64` values but rather `Node`s that output `f64` values when evaluated (in this case, with an empty input of `()`).
|
/// for two `f64` parameter (secondary input) types. Since Graphene works by having each function evaluate its upstream node as a lambda that returns output data, these secondary inputs are not directly `f64` values but rather `Node`s that output `f64` values when evaluated (in this case, with an empty input of `()`).
|
||||||
/// - Mapping the function's return type to the impl'd `Node` trait's associated type, e.g.:
|
/// - Mapping the function's return type to the impl'd `Node` trait's associated type, e.g.:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// Output = Color
|
/// Output = Color
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -67,7 +67,7 @@ use syn::{
|
|||||||
///
|
///
|
||||||
/// The function body runs with the actual primary input value from the `eval` method's argument and the secondary input values from the `eval` method's `let` declarations. The result looks like this:
|
/// The function body runs with the actual primary input value from the `eval` method's argument and the secondary input values from the `eval` method's `let` declarations. The result looks like this:
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```ignore
|
||||||
/// fn eval(&'input self, color: Color) -> Self::Output {
|
/// fn eval(&'input self, color: Color) -> Self::Output {
|
||||||
/// let secondaryA = self.secondaryA.eval(());
|
/// let secondaryA = self.secondaryA.eval(());
|
||||||
/// let secondaryB = self.secondaryB.eval(());
|
/// let secondaryB = self.secondaryB.eval(());
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
mod as_message;
|
mod as_message;
|
||||||
mod combined_message_attrs;
|
mod combined_message_attrs;
|
||||||
mod discriminant;
|
mod discriminant;
|
||||||
|
|||||||
+25
-25
@@ -125,62 +125,58 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||||||
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 8" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Editable nested node subgraphs</span>
|
<span>Editable nested node subgraphs</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-icon complete" title="Development Complete">
|
||||||
|
<img class="atlas" style="--atlas-index: 10" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
|
<span>Procedurally alterable vector data</span>
|
||||||
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 0" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Imaginate (Stable Diffusion node/tool)</span>
|
<span>Imaginate (Stable Diffusion node/tool)</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
|
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
|
<span>New vector 2D renderer (with <a target="_blank" href="https://github.com/linebender/vello">Vello</a>)</span>
|
||||||
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 12" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>GPU-accelerated raster rendering</span>
|
<span>GPU-accelerated raster rendering</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 41" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 19" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Timeline with animation channels</span>
|
<span>Imported RAW photo processing</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 26" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 26" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Interactive graph auto-layout</span>
|
<span>Interactive graph auto-layout</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
|
||||||
<img class="atlas" style="--atlas-index: 19" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
|
||||||
<span>Imported RAW photo processing</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon ongoing" title="Development Ongoing">
|
||||||
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 14" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Adaptive resolution system</span>
|
<span>Adaptive resolution system</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 49" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 41" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>AI nodes and tools (e.g. magic wand)</span>
|
<span>Timeline with animation channels</span>
|
||||||
</div>
|
|
||||||
<div class="feature-icon ongoing" title="Development Ongoing">
|
|
||||||
<img class="atlas" style="--atlas-index: 10" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
|
||||||
<span>Procedurally alterable vector data</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 7" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Native desktop app (with <a target="_blank" href="https://tauri.app/">Tauri</a>)</span>
|
<span>Native desktop app (with <a target="_blank" href="https://tauri.app/">Tauri</a>)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 13" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 54" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>New vector 2D renderer (with <a target="_blank" href="https://github.com/linebender/vello">Vello</a>)</span>
|
<span>Local file browser for saving/loading</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 5" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Fully-supported brush tool</span>
|
<span>Fully-supported brush tool</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="feature-icon">
|
||||||
|
<img class="atlas" style="--atlas-index: 21" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
|
<span>Select mode (marquee masking)</span>
|
||||||
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 9" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Graph data attribute spreadsheet</span>
|
<span>Graph data attribute spreadsheet</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
|
||||||
<img class="atlas" style="--atlas-index: 54" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
|
||||||
<span>Local file browser for saving/loading</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature-icon">
|
|
||||||
<img class="atlas" style="--atlas-index: 53" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
|
||||||
<span>Local fonts access</span>
|
|
||||||
</div>
|
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 17" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Stable document format</span>
|
<span>Stable document format</span>
|
||||||
@@ -190,8 +186,12 @@ Always on the bleeding edge and built to last— Graphite is written on a robust
|
|||||||
<h3>— Alpha 4 —</h3>
|
<h3>— Alpha 4 —</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 21" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 49" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
<span>Select mode (marquee masking)</span>
|
<span>AI nodes and tools (e.g. magic wand)</span>
|
||||||
|
</div>
|
||||||
|
<div class="feature-icon">
|
||||||
|
<img class="atlas" style="--atlas-index: 53" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
|
<span>Local fonts access</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<img class="atlas" style="--atlas-index: 52" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
<img class="atlas" style="--atlas-index: 52" src="https://static.graphite.rs/icons/icon-atlas-roadmap__2.png" alt="" />
|
||||||
|
|||||||
Reference in New Issue
Block a user