Merge branch 'master' into fix-range

This commit is contained in:
Keavon Chambers
2025-07-08 18:23:21 -07:00
committed by GitHub
57 changed files with 2713 additions and 2181 deletions
Generated
+982 -769
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
pub const PIVOT_DIAMETER: f64 = 5.; pub const PIVOT_DIAMETER: f64 = 5.;
pub const DOWEL_PIN_RADIUS: f64 = 4.;
// COMPASS ROSE // COMPASS ROSE
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
// COLORS // COLORS
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
pub const COLOR_OVERLAY_RED: &str = "#ef5454"; pub const COLOR_OVERLAY_RED: &str = "#ef5454";
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc"; pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
+2
View File
@@ -140,6 +140,7 @@ impl Dispatcher {
let graphene_std::renderer::RenderMetadata { let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
click_targets, click_targets,
clip_targets, clip_targets,
} = render_metadata; } = render_metadata;
@@ -149,6 +150,7 @@ impl Dispatcher {
DocumentMessage::UpdateUpstreamTransforms { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
}, },
DocumentMessage::UpdateClickTargets { click_targets }, DocumentMessage::UpdateClickTargets { click_targets },
DocumentMessage::UpdateClipTargets { clip_targets }, DocumentMessage::UpdateClipTargets { clip_targets },
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
pub disabled: bool, pub disabled: bool,
pub tooltip: String,
// Callbacks // Callbacks
#[serde(skip)] #[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
UpdateUpstreamTransforms { UpdateUpstreamTransforms {
upstream_footprints: HashMap<NodeId, Footprint>, upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>, local_transforms: HashMap<NodeId, DAffine2>,
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
}, },
UpdateClickTargets { UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>, click_targets: HashMap<NodeId, Vec<ClickTarget>>,
@@ -750,6 +750,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// Nudge translation without resizing // Nudge translation without resizing
if !resize { if !resize {
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y))); let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) { for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
responses.add(GraphOperationMessage::TransformChange { responses.add(GraphOperationMessage::TransformChange {
@@ -1185,6 +1186,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible, OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible, OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
OverlaysType::Pivot => visibility_settings.pivot = visible, OverlaysType::Pivot => visibility_settings.pivot = visible,
OverlaysType::Origin => visibility_settings.origin = visible,
OverlaysType::Path => visibility_settings.path = visible, OverlaysType::Path => visibility_settings.path = visible,
OverlaysType::Anchors => { OverlaysType::Anchors => {
visibility_settings.anchors = visible; visibility_settings.anchors = visible;
@@ -1305,8 +1307,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::UpdateUpstreamTransforms { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints, upstream_footprints,
local_transforms, local_transforms,
first_instance_source_id,
} => { } => {
self.network_interface.update_transforms(upstream_footprints, local_transforms); self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id);
} }
DocumentMessage::UpdateClickTargets { click_targets } => { DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets // TODO: Allow non layer nodes to have click targets
@@ -1714,6 +1718,14 @@ impl DocumentMessageHandler {
.reduce(graphene_std::renderer::Quad::combine_bounds) .reduce(graphene_std::renderer::Quad::combine_bounds)
} }
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&self.network_interface)
.map(|layer| self.metadata().nonzero_bounding_box(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn document_network(&self) -> &NodeNetwork { pub fn document_network(&self) -> &NodeNetwork {
self.network_interface.document_network() self.network_interface.document_network()
} }
@@ -2267,6 +2279,24 @@ impl DocumentMessageHandler {
] ]
}, },
}, },
LayoutGroup::Row {
widgets: {
let mut checkbox_id = CheckboxId::default();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Origin),
}
.into()
})
.for_label(checkbox_id.clone())
.widget_holder(),
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
]
},
},
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let mut checkbox_id = CheckboxId::default();
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, IVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::Artboard; use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
transform_in: TransformIn, transform_in: TransformIn,
skip_rerender: bool, skip_rerender: bool,
}, },
TransformSetPivot {
layer: LayerNodeIdentifier,
pivot: DVec2,
},
Vector { Vector {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
modification_type: VectorModificationType, modification_type: VectorModificationType,
@@ -89,15 +89,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
modify_inputs.transform_set(transform, transform_in, skip_rerender); modify_inputs.transform_set(transform, transform_in, skip_rerender);
} }
} }
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.pivot_set(pivot);
}
}
GraphOperationMessage::Vector { layer, modification_type } => { 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");
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector}; use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, IVec2};
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
@@ -97,6 +97,8 @@ impl<'a> ModifyInputsContext<'a> {
}; };
} }
let layer_input_connector = post_node_input_connector.clone();
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer // Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
loop { loop {
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]); let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);
@@ -105,6 +107,11 @@ impl<'a> ModifyInputsContext<'a> {
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => { Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
// Update post_node_input_connector for the next iteration // Update post_node_input_connector for the next iteration
post_node_input_connector = InputConnector::node(pre_node_id, 0); post_node_input_connector = InputConnector::node(pre_node_id, 0);
// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input
let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());
if !primary_is_exposed {
return layer_input_connector;
}
} }
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer _ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
} }
@@ -451,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
} }
} }
pub fn pivot_set(&mut self, new_pivot: DVec2) {
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
}
pub fn vector_modify(&mut self, modification_type: VectorModificationType) { pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
let Some(path_node_id) = self.existing_node_id("Path", true) else { return }; let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
self.network_interface.vector_modify(&path_node_id, modification_type); self.network_interface.vector_modify(&path_node_id, modification_type);
@@ -21,6 +21,7 @@ use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)]
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::VectorDataTable; use graphene_std::vector::VectorDataTable;
use graphene_std::*; use graphene_std::*;
@@ -88,7 +89,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "General", category: "General",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)], inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default() ..Default::default()
}, },
@@ -107,7 +108,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)], inputs: vec![NodeInput::value(TaggedValue::None, true)],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
@@ -148,19 +149,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -230,21 +231,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Secondary (left) input type coercion // Secondary (left) input type coercion
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 1)], inputs: vec![NodeInput::network(generic!(T), 1)],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
// Primary (bottom) input type coercion // Primary (bottom) input type coercion
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
// The monitor node is used to display a thumbnail in the UI // The monitor node is used to display a thumbnail in the UI
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -256,7 +257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(2), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -337,7 +338,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1), NodeInput::network(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false), NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
@@ -352,7 +353,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: Check if thumbnail is reversed // TODO: Check if thumbnail is reversed
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -364,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::AppendArtboardNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -466,13 +467,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -522,6 +523,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Loads an image from a given URL"), description: Cow::Borrowed("Loads an image from a given URL"),
properties: None, properties: None,
}, },
#[cfg(feature = "gpu")]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Create Canvas", identifier: "Create Canvas",
category: "Debug: GPU", category: "Debug: GPU",
@@ -532,14 +534,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -587,99 +589,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Creates a new canvas object."), description: Cow::Borrowed("Creates a new canvas object."),
properties: None, properties: None,
}, },
DocumentNodeDefinition { #[cfg(all(feature = "gpu", target_arch = "wasm32"))]
identifier: "Draw Canvas",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, RasterDataTable<SRGBA8>>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DrawImageFrameNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Canvas".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Into".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Draw Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("Draws raster data to a canvas element."),
properties: None,
},
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Rasterize", identifier: "Rasterize",
category: "Raster", category: "Raster",
@@ -690,20 +600,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@@ -778,7 +688,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_raster_nodes::std_nodes::NoisePatternNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::Bool(true), false), NodeInput::value(TaggedValue::Bool(true), false),
@@ -843,7 +753,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -852,7 +762,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -861,7 +771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -870,7 +780,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -948,13 +858,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1024,7 +934,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(BrushCache), 2), NodeInput::network(concrete!(BrushCache), 2),
], ],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_brush::brush::BrushNode")), implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
..Default::default() ..Default::default()
}] }]
.into_iter() .into_iter()
@@ -1072,7 +982,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
@@ -1091,7 +1001,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
@@ -1105,164 +1015,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"), description: Cow::Borrowed("TODO"),
properties: None, properties: None,
}, },
DocumentNodeDefinition {
identifier: "Storage",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Storage".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Storage".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Create Output Buffer",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true), NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
output_names: vec!["Output Buffer".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Output Buffer".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Create GPU Surface", identifier: "Create GPU Surface",
@@ -1275,13 +1027,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1329,87 +1081,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"), description: Cow::Borrowed("TODO"),
properties: None, properties: None,
}, },
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "Upload Texture",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(generic!(T)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Upload Texture".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Extract", identifier: "Extract",
category: "Debug", category: "Debug",
@@ -1466,15 +1137,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)], inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1532,7 +1207,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Text", category: "Text",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"), implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![ inputs: vec![
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
@@ -1633,14 +1308,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false),
], ],
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -1652,10 +1326,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(DVec2), 3), NodeInput::network(concrete!(DVec2), 3),
NodeInput::network(concrete!(DVec2), 4), NodeInput::network(concrete!(DVec2), 4),
NodeInput::network(concrete!(DVec2), 5),
], ],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1720,7 +1393,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}), }),
), ),
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
], ],
output_names: vec!["Data".to_string()], output_names: vec!["Data".to_string()],
..Default::default() ..Default::default()
@@ -1739,25 +1411,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")), implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1837,7 +1509,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1852,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(bool), 6), NodeInput::network(concrete!(bool), 6),
NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(3), 0)], inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -2008,24 +1680,24 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(u32), 2), NodeInput::network(concrete!(u32), 2),
], ],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::proto("graphene_core::vector::PoissonDiskPointsNode"), implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -2645,11 +2317,11 @@ pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefin
pub fn collect_node_types() -> Vec<FrontendNodeType> { pub fn collect_node_types() -> Vec<FrontendNodeType> {
// Create a mapping from registry ID to document node identifier // Create a mapping from registry ID to document node identifier
let id_to_identifier_map: HashMap<String, &'static str> = DOCUMENT_NODE_TYPES let id_to_identifier_map: HashMap<ProtoNodeIdentifier, &'static str> = DOCUMENT_NODE_TYPES
.iter() .iter()
.filter_map(|definition| { .filter_map(|definition| {
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &definition.node_template.document_node.implementation { if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation {
Some((name.to_string(), definition.identifier)) Some((name.clone(), definition.identifier))
} else { } else {
None None
} }
@@ -2657,28 +2329,28 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
.collect(); .collect();
let mut extracted_node_types = Vec::new(); let mut extracted_node_types = Vec::new();
let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = registry::NODE_REGISTRY.lock().unwrap();
let node_metadata = graphene_std::registry::NODE_METADATA.lock().unwrap(); let node_metadata = registry::NODE_METADATA.lock().unwrap();
for (id, metadata) in node_metadata.iter() { for (id, metadata) in node_metadata.iter() {
if let Some(implementations) = node_registry.get(id) { if let Some(implementations) = node_registry.get(id) {
let identifier = match id_to_identifier_map.get(id) { let identifier = match id_to_identifier_map.get(id) {
Some(&id) => id.to_string(), Some(&id) => id,
None => continue, None => continue,
}; };
// Extract category from metadata (already creates an owned String) // Extract category from metadata (already creates an owned String)
let category = metadata.category.unwrap_or_default().to_string(); let category = metadata.category.unwrap_or_default();
// Extract input types (already creates owned Strings) // Extract input types (already creates owned Strings)
let input_types = implementations let input_types = implementations
.iter() .iter()
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string())) .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string()))
.collect::<HashSet<String>>() .collect::<HashSet<Cow<'static, str>>>()
.into_iter() .into_iter()
.collect::<Vec<String>>(); .collect::<Vec<Cow<'static, str>>>();
// Create a FrontendNodeType // Create a FrontendNodeType
let node_type = FrontendNodeType::with_owned_strings_and_input_types(identifier, category, input_types); let node_type = FrontendNodeType::with_input_types(identifier, category, input_types);
// Store the created node_type // Store the created node_type
extracted_node_types.push(node_type); extracted_node_types.push(node_type);
@@ -2694,8 +2366,8 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
.document_node .document_node
.inputs .inputs
.iter() .iter()
.filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string())) .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string()))
.collect::<Vec<String>>(); .collect::<Vec<Cow<'static, str>>>();
FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types) FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types)
}) })
@@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
}; };
} }
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = NODE_REGISTRY.lock().unwrap();
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
for node in custom.iter() { for node in custom.iter() {
let DocumentNodeDefinition { let DocumentNodeDefinition {
@@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
.. ..
} = node; } = node;
match implementation { match implementation {
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer, DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
_ => (), _ => (),
} }
} }
@@ -1436,7 +1436,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if let Some(field) = graphene_std::registry::NODE_METADATA if let Some(field) = graphene_std::registry::NODE_METADATA
.lock() .lock()
.unwrap() .unwrap()
.get(&proto_node_identifier.name.clone().into_owned()) .get(&proto_node_identifier)
.and_then(|metadata| metadata.fields.get(input_index)) .and_then(|metadata| metadata.fields.get(input_index))
{ {
number_options = (field.number_min, field.number_max, field.number_mode_range); number_options = (field.number_min, field.number_max, field.number_mode_range);
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::Type; use graphene_std::Type;
use std::borrow::Cow;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType { pub enum FrontendGraphDataType {
@@ -98,33 +99,25 @@ pub struct FrontendNode {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType { pub struct FrontendNodeType {
pub name: String, pub name: Cow<'static, str>,
pub category: String, pub category: Cow<'static, str>,
#[serde(rename = "inputTypes")] #[serde(rename = "inputTypes")]
pub input_types: Option<Vec<String>>, pub input_types: Option<Vec<Cow<'static, str>>>,
} }
impl FrontendNodeType { impl FrontendNodeType {
pub fn new(name: &'static str, category: &'static str) -> Self { pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
Self { Self {
name: name.to_string(), name: name.into(),
category: category.to_string(), category: category.into(),
input_types: None, input_types: None,
} }
} }
pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec<String>) -> Self { pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
Self { Self {
name: name.to_string(), name: name.into(),
category: category.to_string(), category: category.into(),
input_types: Some(input_types),
}
}
pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec<String>) -> Self {
Self {
name,
category,
input_types: Some(input_types), input_types: Some(input_types),
} }
} }
@@ -198,7 +191,7 @@ pub struct FrontendClickTargets {
pub modify_import_export: Vec<String>, pub modify_import_export: Vec<String>,
} }
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum Direction { pub enum Direction {
Up, Up,
Down, Down,
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
if display_path { if display_path {
overlay_context.outline_vector(&vector_data, transform); overlay_context.outline_vector(&vector_data, transform);
} }
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
continue; continue;
}; };
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
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_point_selected(point)); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
@@ -1,12 +1,12 @@
use super::utility_functions::overlay_canvas_context; use super::utility_functions::overlay_canvas_context;
use crate::consts::{ use crate::consts::{
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, 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::{Bezier, Subpath}; use bezier_rs::{Bezier, Subpath};
use core::borrow::Borrow; use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, TAU}; use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
@@ -33,12 +33,14 @@ pub enum OverlaysType {
HoverOutline, HoverOutline,
SelectionOutline, SelectionOutline,
Pivot, Pivot,
Origin,
Path, Path,
Anchors, Anchors,
Handles, Handles,
} }
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(default)]
pub struct OverlaysVisibilitySettings { pub struct OverlaysVisibilitySettings {
pub all: bool, pub all: bool,
pub artboard_name: bool, pub artboard_name: bool,
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
pub hover_outline: bool, pub hover_outline: bool,
pub selection_outline: bool, pub selection_outline: bool,
pub pivot: bool, pub pivot: bool,
pub origin: bool,
pub path: bool, pub path: bool,
pub anchors: bool, pub anchors: bool,
pub handles: bool, pub handles: bool,
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
hover_outline: true, hover_outline: true,
selection_outline: true, selection_outline: true,
pivot: true, pivot: true,
origin: true,
path: true, path: true,
anchors: true, anchors: true,
handles: true, handles: true,
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
self.all && self.pivot self.all && self.pivot
} }
pub fn origin(&self) -> bool {
self.all && self.origin
}
pub fn path(&self) -> bool { pub fn path(&self) -> bool {
self.all && self.path self.all && self.path
} }
@@ -423,10 +431,7 @@ impl OverlayContext {
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
let sign = scale.signum(); let sign = scale.signum();
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
.unwrap()
.with_alpha(0.05)
.to_rgba_hex_srgb();
fill_color.insert(0, '#'); fill_color.insert(0, '#');
let fill_color = Some(fill_color.as_str()); let fill_color = Some(fill_color.as_str());
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
@@ -463,10 +468,7 @@ impl OverlayContext {
// Hover ring // Hover ring
if show_hover_ring { if show_hover_ring {
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
.unwrap()
.with_alpha(0.5)
.to_rgba_hex_srgb();
fill_color.insert(0, '#'); fill_color.insert(0, '#');
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
@@ -550,6 +552,36 @@ impl OverlayContext {
self.end_dpi_aware_transform(); self.end_dpi_aware_transform();
} }
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
self.start_dpi_aware_transform();
// Draw the background circle with a white fill and blue outline
self.render_context.begin_path();
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
self.render_context.fill();
self.render_context.set_stroke_style_str(color);
self.render_context.stroke();
// Draw the two blue filled sectors
self.render_context.begin_path();
// Top-left sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
self.render_context.close_path();
// Bottom-right sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
self.render_context.close_path();
self.render_context.set_fill_style_str(color);
self.render_context.fill();
self.end_dpi_aware_transform();
}
/// Used by the Pen and Path tools to outline the path of the shape. /// Used by the Pen and Path tools to outline the path of the shape.
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
self.start_dpi_aware_transform(); self.start_dpi_aware_transform();
@@ -599,9 +631,11 @@ impl OverlayContext {
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) { pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
self.start_dpi_aware_transform(); self.start_dpi_aware_transform();
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
self.render_context.begin_path(); self.render_context.begin_path();
self.bezier_command(bezier, transform, true); self.bezier_command(bezier, transform, true);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50); self.render_context.set_stroke_style_str(&color);
self.render_context.set_line_width(4.); self.render_context.set_line_width(4.);
self.render_context.stroke(); self.render_context.stroke();
@@ -731,11 +765,11 @@ impl OverlayContext {
// └──┴──┴──┴──┘ // └──┴──┴──┴──┘
let pixels = [(0, 0), (2, 2)]; let pixels = [(0, 0), (2, 2)];
for &(x, y) in &pixels { for &(x, y) in &pixels {
let index = (x + y * PATTERN_WIDTH as usize) * 4; let index = (x + y * PATTERN_WIDTH) * 4;
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb()); data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
} }
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
pattern_context.put_image_data(&image_data, 0., 0.).unwrap(); pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap(); let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
@@ -1,6 +1,8 @@
use super::network_interface::NodeNetworkInterface; use super::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync), // TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
// TODO: it might be better to have a system that can query the state of the node network on demand. // TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct DocumentMetadata { pub struct DocumentMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>, pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
pub document_to_viewport: DAffine2, pub document_to_viewport: DAffine2,
} }
impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
structure: HashMap::new(),
vector_modify: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
document_to_viewport: DAffine2::IDENTITY,
}
}
}
// ================================= // =================================
// DocumentMetadata: Layer iterators // DocumentMetadata: Layer iterators
// ================================= // =================================
@@ -91,6 +80,36 @@ impl DocumentMetadata {
footprint * local_transform footprint * local_transform
} }
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
// We're not allowed to convert the root parent to a node id
if layer == LayerNodeIdentifier::ROOT_PARENT {
return self.document_to_viewport;
}
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
if !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
.any(|upstream| Some(upstream) == source)
{
use_local = false;
info!("Local transform is invalid — using the identity for the local transform instead")
}
}
}
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
footprint * local_transform
}
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
}
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let local_transform = self.local_transforms.get(&layer.to_node()).copied(); let local_transform = self.local_transforms.get(&layer.to_node()).copied();
@@ -60,6 +60,27 @@ impl PartialEq for NodeNetworkInterface {
} }
} }
impl NodeNetworkInterface {
/// Add DocumentNodePath input to the PathModifyNode protonode
pub fn migrate_path_modify_node(&mut self) {
fix_network(&mut self.network);
fn fix_network(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
if let Some(network) = node.implementation.get_network_mut() {
fix_network(network);
}
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
if protonode.name.contains("PathModifyNode") {
if node.inputs.len() < 3 {
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
}
}
}
}
}
}
}
// Public immutable getters for the network interface // Public immutable getters for the network interface
impl NodeNetworkInterface { impl NodeNetworkInterface {
// TODO: Make private and use .field_name getter methods // TODO: Make private and use .field_name getter methods
@@ -653,7 +674,7 @@ impl NodeNetworkInterface {
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type)
}); });
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
}) })
@@ -3513,6 +3534,11 @@ impl NodeNetworkInterface {
self.document_metadata.local_transforms = local_transforms; self.document_metadata.local_transforms = local_transforms;
} }
/// Update the cached first instance source id of the layers
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_instance_source_ids = new;
}
/// Update the cached click targets of the layers /// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) { pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
self.document_metadata.click_targets = new_click_targets; self.document_metadata.click_targets = new_click_targets;
@@ -4093,6 +4119,18 @@ impl NodeNetworkInterface {
} }
} }
// When opening an old document to ensure the output names match the number of exports
pub fn validate_output_names(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) {
if let DocumentNodeImplementation::Network(network) = &node.implementation {
let number_of_exports = network.exports.len();
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("Could not get metadata for node: {:?}", node_id);
return;
};
metadata.persistent_metadata.output_names.resize(number_of_exports, "".to_string());
}
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_reference_name(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: String) { pub fn replace_reference_name(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: String) {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
@@ -5639,7 +5677,6 @@ impl NodeNetworkInterface {
self.unload_all_nodes_bounding_box(network_path); self.unload_all_nodes_bounding_box(network_path);
} }
// TODO: Run the auto layout system to make space for the new nodes
/// Disconnect the layers primary output and the input to the last non layer node feeding into it through primary flow, reconnects, then moves the layer to the new layer and stack index /// Disconnect the layers primary output and the input to the last non layer node feeding into it through primary flow, reconnects, then moves the layer to the new layer and stack index
pub fn move_layer_to_stack(&mut self, layer: LayerNodeIdentifier, mut parent: LayerNodeIdentifier, mut insert_index: usize, network_path: &[NodeId]) { pub fn move_layer_to_stack(&mut self, layer: LayerNodeIdentifier, mut parent: LayerNodeIdentifier, mut insert_index: usize, network_path: &[NodeId]) {
// Prevent moving an artboard anywhere but to the ROOT_PARENT child stack // Prevent moving an artboard anywhere but to the ROOT_PARENT child stack
@@ -1,5 +1,7 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface; use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
@@ -98,6 +100,22 @@ impl SelectedNodes {
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface)) .filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
} }
pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
let (sum, count) = self
.selected_visible_and_unlocked_layers(network_interface)
.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
}
pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
let (sum, count) = self
.selected_visible_and_unlocked_layers(network_interface)
.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
}
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a { pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node())) metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
} }
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn}; use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, 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::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
@@ -537,17 +536,6 @@ impl<'a> Selected<'a> {
} }
} }
pub fn mean_average_of_pivots(&mut self) -> DVec2 {
let xy_summation = self
.selected
.iter()
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
xy_summation / self.selected.len() as f64
}
pub fn center_of_aabb(&mut self) -> DVec2 { pub fn center_of_aabb(&mut self) -> DVec2 {
let [min, max] = self let [min, max] = self
.selected .selected
@@ -7,6 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
use crate::messages::prelude::DocumentMessageHandler; use crate::messages::prelude::DocumentMessageHandler;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::IVec2; use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::text::TypesettingConfig; use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
@@ -190,6 +191,8 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
} }
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
document.network_interface.migrate_path_modify_node();
let network = document.network_interface.document_network().clone(); let network = document.network_interface.document_network().clone();
// Apply string replacements to each node // Apply string replacements to each node
@@ -215,469 +218,476 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.map(|(node_id, node, path)| (*node_id, node.clone(), path)) .map(|(node_id, node, path)| (*node_id, node.clone(), path))
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>(); .collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
for (node_id, node, network_path) in &nodes { for (node_id, node, network_path) in &nodes {
if reset_node_definitions_on_open { migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { }
let Some(node_definition) = resolve_document_node_type(reference) else { continue }; }
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
} fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> {
if reset_node_definitions_on_open {
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
let node_definition = resolve_document_node_type(reference)?;
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
}
}
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
document
.network_interface
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
}
// Only nodes that have not been modified and still refer to a definition can be updated
let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?;
let reference = &reference;
let inputs_count = node.inputs.len();
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
if reference == "Stroke" && inputs_count == 8 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path);
}
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node
if reference == "Splines from Points" {
document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string()));
}
// Upgrade the old "Spline" node to the new "Spline" node
if reference == "Spline" {
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node
let identifier = document
.network_interface
.implementation(node_id, network_path)
.and_then(|implementation| implementation.get_proto_node());
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
return None;
} }
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { let node = document.network_interface.document_node(node_id, network_path)?;
document let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
.network_interface log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); return None;
}
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
// Only nodes that have not been modified and still refer to a definition can be updated
continue;
}; };
let reference = &reference; let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
let inputs_count = node.inputs.len(); // Retrieve the output connectors linked to the "Spline" node's output port
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
return None;
};
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) // Get the node's current position in the graph
if reference == "Stroke" && inputs_count == 8 { let Some(node_position) = document.network_interface.position(node_id, network_path) else {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); log::error!("Could not get position of spline node.");
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); return None;
};
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); // Get the "Path" node definition and fill it in with the vector data and default vector modification
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); let Some(path_node_type) = resolve_document_node_type("Path") else {
log::error!("Path node does not exist.");
return None;
};
let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); // Get the "Spline" node definition and wire it up with the "Path" node as input
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); let Some(spline_node_type) = resolve_document_node_type("Spline") else {
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); log::error!("Spline node does not exist.");
document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); return None;
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); };
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
let new_spline_id = *new_ids.get(&NodeId(0))?;
let new_path_id = *new_ids.get(&NodeId(1))?;
// Remove the old "Spline" node from the document
document.network_interface.delete_nodes(vec![*node_id], false, network_path);
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs
document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path);
// Reposition the new "Spline" node to match the original "Spline" node's position
document.network_interface.shift_node(&new_spline_id, node_position, network_path);
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
// Redirect each output connection from the old node to the new "Spline" node's output port
for input_connector in spline_outputs {
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
} }
}
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Splines from Points" { if reference == "Text" && inputs_count != 9 {
document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string())); let mut template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node(*node_id, 4),
if inputs_count == 6 {
old_inputs[4].clone()
} else {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false)
},
network_path,
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 5),
if inputs_count == 6 {
old_inputs[5].clone()
} else {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false)
},
network_path,
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 6),
if inputs_count >= 7 {
old_inputs[6].clone()
} else {
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
},
network_path,
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 7),
if inputs_count >= 8 {
old_inputs[7].clone()
} else {
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
},
network_path,
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 8),
if inputs_count >= 9 {
old_inputs[8].clone()
} else {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
},
network_path,
);
}
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path);
}
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
if reference == "Modulo" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
}
// Upgrade the Mirror node to add the `keep_original` boolean input
if reference == "Mirror" && inputs_count == 3 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path);
}
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
if reference == "Mirror" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None };
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node(*node_id, 1),
NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false),
network_path,
);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
}
// Upgrade artboard name being passed as hidden value input to "To Artboard"
if reference == "Artboard" && reset_node_definitions_on_open {
let label = document.network_interface.display_name(node_id, network_path);
document
.network_interface
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
}
if reference == "Image" && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
// Insert a new empty input for the image
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
document.network_interface.set_reference(node_id, network_path, Some("Image".to_string()));
}
if reference == "Noise Pattern" && inputs_count == 15 {
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document
.network_interface
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);
for (i, input) in old_inputs.iter().enumerate() {
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);
} }
}
// Upgrade the old "Spline" node to the new "Spline" node if reference == "Instance on Points" && inputs_count == 2 {
if reference == "Spline" { let mut node_template = resolve_document_node_type(reference)?.default_node_template();
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let identifier = document
.network_interface let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
.implementation(node_id, network_path)
.and_then(|implementation| implementation.get_proto_node()); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
continue; }
}
if reference == "Morph" && inputs_count == 4 {
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let node = document.network_interface.document_node(node_id, network_path).unwrap(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
continue;
}; document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
// Retrieve the output connectors linked to the "Spline" node's output port // We have removed the last input, so we don't add index 3
let spline_outputs = document }
.network_interface
.outward_wires(network_path) if reference == "Brush" && inputs_count == 4 {
.unwrap() let mut node_template = resolve_document_node_type(reference)?.default_node_template();
.get(&OutputConnector::node(*node_id, 0)) document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
.clone(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
// Get the node's current position in the graph document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
let Some(node_position) = document.network_interface.position(node_id, network_path) else { // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
log::error!("Could not get position of spline node."); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
continue; document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
}; }
// Get the "Path" node definition and fill it in with the vector data and default vector modification if reference == "Flatten Vector Elements" {
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist."); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let path_node = path_node_type.node_template_input_override([ document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
]);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// Get the "Spline" node definition and wire it up with the "Path" node as input
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist."); document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); }
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them if reference == "Remove Handles" {
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
let new_path_id = *new_ids.get(&NodeId(1)).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
// Remove the old "Spline" node from the document document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.delete_nodes(vec![*node_id], false, network_path); document
.network_interface
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); document
.network_interface
// Reposition the new "Spline" node to match the original "Spline" node's position .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
document.network_interface.shift_node(&new_spline_id, node_position, network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position }
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
if reference == "Generate Handles" {
// Redirect each output connection from the old node to the new "Spline" node's output port let mut node_template = resolve_document_node_type("Auto-Tangents")?.default_node_template();
for input_connector in spline_outputs { document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
} let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
}
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
if reference == "Text" && inputs_count != 9 { document
let mut template = resolve_document_node_type(reference).unwrap().default_node_template(); .network_interface
document.network_interface.replace_implementation(node_id, network_path, &mut template); .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap();
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); }
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); if reference == "Merge by Distance" && inputs_count == 2 {
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.set_input( document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
&InputConnector::node(*node_id, 4),
if inputs_count == 6 { let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
old_inputs[4].clone()
} else { document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
}, document.network_interface.set_input(
network_path, &InputConnector::node(*node_id, 2),
); NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
document.network_interface.set_input( network_path,
&InputConnector::node(*node_id, 5), );
if inputs_count == 6 { }
old_inputs[5].clone()
} else { if reference == "Spatial Merge by Distance" {
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) let mut node_template = resolve_document_node_type("Merge by Distance")?.default_node_template();
}, document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
network_path,
); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(
&InputConnector::node(*node_id, 6), document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
if inputs_count >= 7 { document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
old_inputs[6].clone() document.network_interface.set_input(
} else { &InputConnector::node(*node_id, 2),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
}, network_path,
network_path, );
);
document.network_interface.set_input( document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
&InputConnector::node(*node_id, 7), }
if inputs_count >= 8 {
old_inputs[7].clone() if reference == "Sample Points" && inputs_count == 5 {
} else { let mut node_template = resolve_document_node_type("Sample Polyline")?.default_node_template();
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
},
network_path, let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
); let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
document.network_interface.set_input( let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
&InputConnector::node(*node_id, 8),
if inputs_count >= 9 { document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
old_inputs[8].clone() document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
} else { document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
}, document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
network_path, document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
); document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
}
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default }
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); // Make the "Quantity" parameter a u32 instead of f64
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); if reference == "Sample Polyline" {
// Get the inputs, obtain the quantity value, and put the inputs back
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let quantity_value = document
.network_interface
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?;
document
.network_interface if let NodeInput::Value { tagged_value, exposed } = quantity_value {
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); if let TaggedValue::F64(value) = **tagged_value {
} let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
if reference == "Modulo" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
}
// Upgrade the Mirror node to add the `keep_original` boolean input
if reference == "Mirror" && inputs_count == 3 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path);
}
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
if reference == "Mirror" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return };
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node(*node_id, 1),
NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false),
network_path,
);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path);
}
// Upgrade artboard name being passed as hidden value input to "To Artboard"
if reference == "Artboard" && reset_node_definitions_on_open {
let label = document.network_interface.display_name(node_id, network_path);
document
.network_interface
.set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]);
}
if reference == "Image" && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
// Insert a new empty input for the image
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
document.network_interface.set_reference(node_id, network_path, Some("Image".to_string()));
}
if reference == "Noise Pattern" && inputs_count == 15 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document
.network_interface
.set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path);
for (i, input) in old_inputs.iter().enumerate() {
document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path);
}
}
if reference == "Instance on Points" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
}
if reference == "Morph" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
// We have removed the last input, so we don't add index 3
}
if reference == "Brush" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path);
}
if reference == "Flatten Vector Elements" {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
}
if reference == "Remove Handles" {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
}
if reference == "Generate Handles" {
let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document
.network_interface
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
}
if reference == "Merge by Distance" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node(*node_id, 2),
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
network_path,
);
}
if reference == "Spatial Merge by Distance" {
let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node(*node_id, 2),
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
network_path,
);
document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string());
}
if reference == "Sample Points" && inputs_count == 5 {
let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
}
// Make the "Quantity" parameter a u32 instead of f64
if reference == "Sample Polyline" {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let mut new_node_template = node_definition.default_node_template();
// Get the inputs, obtain the quantity value, and put the inputs back
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
let quantity_value = old_inputs.get(3).cloned();
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
if let TaggedValue::F64(value) = *tagged_value {
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
}
}
}
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap();
let mut new_node_template = node_definition.default_node_template();
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
let index_3_value = old_inputs.get(3).cloned();
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
// Move index 3 to the end
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
} else {
// Swap it back if we're not changing anything
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
}
}
}
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers {
let Some((downstream_node, input_index)) = document
.network_interface
.outward_wires(&[])
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
.and_then(|outward_wires| outward_wires.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
else {
continue;
};
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
continue;
};
if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
}
} }
} }
} }
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference)?;
let mut new_node_template = node_definition.default_node_template();
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?;
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?;
let index_3_value = old_inputs.get(3).cloned();
let mut upgraded = false;
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
// Move index 3 to the end
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
upgraded = true;
}
}
if !upgraded {
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
}
}
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers {
let (downstream_node, input_index) = document
.network_interface
.outward_wires(&[])
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
.and_then(|outward_wires| outward_wires.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?;
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
return None;
};
if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
}
}
}
Some(())
} }
@@ -431,6 +431,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() { for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
document.network_interface.validate_input_metadata(node_id, node, &path); document.network_interface.validate_input_metadata(node_id, node, &path);
document.network_interface.validate_display_name_metadata(node_id, &path); document.network_interface.validate_display_name_metadata(node_id, &path);
document.network_interface.validate_output_names(node_id, node, &path);
} }
// Ensure layers are positioned as stacks if they are upstream siblings of another layer // Ensure layers are positioned as stacks if they are upstream siblings of another layer
@@ -1,5 +1,4 @@
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::DocumentMessageHandler; use crate::messages::prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::f64::consts::FRAC_PI_2; use std::f64::consts::FRAC_PI_2;
@@ -10,25 +9,32 @@ pub struct CompassRose {
} }
impl CompassRose { impl CompassRose {
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) { pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
let selected_nodes = document.network_interface.selected_nodes(); let selected = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else { return }; if !selected.has_selected_nodes() {
let count = layers.count() + 1; return;
let transform = if count == 1 { }
Self::get_layer_pivot_transform(first, document)
} else { let transform = selected
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); .selected_visible_and_unlocked_layers(&document.network_interface)
DAffine2::from_translation(min) * DAffine2::from_scale(max - min) .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
}; .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
.unwrap_or_default();
let bounds = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.filter_map(|layer| {
document
.metadata()
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
})
.reduce(graphene_std::renderer::Quad::combine_bounds);
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
self.compass_center = transform.transform_point2(DVec2::splat(0.5)); self.compass_center = transform.transform_point2(DVec2::splat(0.5));
} }
@@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::DVec2; use glam::DVec2;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
@@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
LayerNodeIdentifier::new_unchecked(id) LayerNodeIdentifier::new_unchecked(id)
} }
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work) /// Locate the origin of the transform node
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> { pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
let pivot_node_input_index = 5; use graphene_std::transform_nodes::transform::TranslateInput;
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
Some(*pivot) if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? {
Some(*origin)
} else { } else {
None None
} }
} }
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let origin = get_origin(layer, network_interface).unwrap_or_default();
network_interface.document_metadata().document_to_viewport.transform_point2(origin)
}
pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer); let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5)); let center = DVec2::splat(0.5);
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot) network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
} }
/// Get the current gradient of a layer from the closest "Fill" node. /// Get the current gradient of a layer from the closest "Fill" node.
@@ -416,14 +422,14 @@ impl<'a> NodeGraphLayer<'a> {
} }
/// Node id of a protonode if it exists in the layer's primary flow /// Node id of a protonode if it exists in the layer's primary flow
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> { pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
self.horizontal_layer_flow() self.horizontal_layer_flow()
// Take until a different layer is reached // Take until a different layer is reached
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[])) .take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
.find(move |node_id| { .find(|node_id| {
self.network_interface self.network_interface
.implementation(node_id, &[]) .implementation(node_id, &[])
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier)) .is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))
}) })
} }
@@ -1,26 +1,184 @@
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.
use super::graph_modification_utils;
use crate::consts::PIVOT_DIAMETER; use crate::consts::PIVOT_DIAMETER;
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::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::transform::ReferencePoint; use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
use std::collections::VecDeque; use std::fmt;
#[derive(Clone, Debug)] pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
.disabled(!enabled)
.on_update(move |_| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
})
.widget_holder()
}
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
ReferencePointInput::new(reference_point)
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
.disabled(disabled)
.on_update(move |pivot_input: &ReferencePointInput| match source {
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),
})
.widget_holder()
}
pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetHolder> {
let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]
.iter()
.map(|gizmo_type| {
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
let value = source.clone();
move |_| match value {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
}
})
})
.collect();
vec![
CheckboxInput::new(!state.disabled)
.tooltip(
"Pivot Gizmo\n\
\n\
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
Disabled: rotation and scaling occurs about the center of the selection bounds.",
)
.on_update(move |optional_input: &CheckboxInput| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
})
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
DropdownInput::new(vec![gizmo_type_entries])
.selected_index(Some(match state.gizmo_type {
PivotGizmoType::Pivot => 0,
PivotGizmoType::Average => 1,
PivotGizmoType::Active => 2,
}))
.tooltip(
"Pivot Gizmo Type\n\
\n\
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
\n\
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
)
.disabled(state.disabled)
.widget_holder(),
]
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub enum PivotToolSource {
Path,
#[default]
Select,
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct PivotGizmo {
pub pivot: Pivot,
pub state: PivotGizmoState,
pub layer: Option<LayerNodeIdentifier>,
pub point: Option<ManipulatorPointId>,
}
impl PivotGizmo {
pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {
let network = &document.network_interface;
(!self.state.disabled)
.then_some({
match self.state.gizmo_type {
PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),
PivotGizmoType::Pivot => self.pivot.pivot,
PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),
}
})
.flatten()
.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))
}
pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {
self.pivot.recalculate_pivot(document);
self.pivot.transform_from_normalized
}
pub fn pin_active(&self) -> bool {
self.pivot.pinned && self.state.is_pivot_type()
}
pub fn pivot_disconnected(&self) -> bool {
self.pivot.old_pivot_position == ReferencePoint::None
}
}
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PivotGizmoType {
// Pivot
#[default]
Pivot,
// Origin
Average,
Active,
// TODO: Add "Individual"
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct PivotGizmoState {
pub disabled: bool,
pub gizmo_type: PivotGizmoType,
}
impl PivotGizmoState {
pub fn is_pivot_type(&self) -> bool {
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
}
pub fn is_pivot(&self) -> bool {
self.gizmo_type == PivotGizmoType::Pivot && !self.disabled
}
}
impl fmt::Display for PivotGizmoType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PivotGizmoType::Pivot => write!(f, "Custom Pivot"),
PivotGizmoType::Average => write!(f, "Origin (Average Point)"),
PivotGizmoType::Active => write!(f, "Origin (Active Object)"),
// TODO: Add "Origin (Individual)"
}
}
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Pivot { pub struct Pivot {
/// Pivot between (0,0) and (1,1) /// Pivot between (0,0) and (1,1)
normalized_pivot: DVec2, normalized_pivot: DVec2,
/// Transform to get from normalized pivot to viewspace /// Transform to get from normalized pivot to viewspace
transform_from_normalized: DAffine2, pub transform_from_normalized: DAffine2,
/// The viewspace pivot position (if applicable) /// The viewspace pivot position
pivot: Option<DVec2>, pub pivot: Option<DVec2>,
/// The old pivot position in the GUI, used to reduce refreshes of the document bar /// The old pivot position in the GUI, used to reduce refreshes of the document bar
old_pivot_position: ReferencePoint, pub old_pivot_position: ReferencePoint,
/// The last ReferencePoint which wasn't none
pub last_non_none_reference_point: ReferencePoint,
/// Used to enable and disable the pivot /// Used to enable and disable the pivot
active: bool, pub pinned: bool,
/// Had selected_visible_and_unlocked_layers
pub empty: bool,
} }
impl Default for Pivot { impl Default for Pivot {
@@ -30,84 +188,62 @@ impl Default for Pivot {
transform_from_normalized: Default::default(), transform_from_normalized: Default::default(),
pivot: Default::default(), pivot: Default::default(),
old_pivot_position: ReferencePoint::Center, old_pivot_position: ReferencePoint::Center,
active: true, last_non_none_reference_point: ReferencePoint::Center,
pinned: false,
empty: true,
} }
} }
} }
impl Pivot { impl Pivot {
/// Calculates the transform that gets from normalized pivot to viewspace.
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}
/// Recomputes the pivot position and transform. /// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
if !self.active { let selected = document.network_interface.selected_nodes();
self.empty = !selected.has_selected_nodes();
if !selected.has_selected_nodes() {
return; return;
} }
let selected_nodes = document.network_interface.selected_nodes(); let transform = selected
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); .selected_visible_and_unlocked_layers(&document.network_interface)
let Some(first) = layers.next() else { .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
// If no layers are selected then we revert things back to default .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
.unwrap_or_default();
let bounds = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.filter_map(|layer| {
document
.metadata()
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
})
.reduce(graphene_std::renderer::Quad::combine_bounds);
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
if self.old_pivot_position != ReferencePoint::None {
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
}
}
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {
let selected = document.network_interface.selected_nodes();
if !selected.has_selected_nodes() {
self.normalized_pivot = DVec2::splat(0.5); self.normalized_pivot = DVec2::splat(0.5);
self.pivot = None; self.pivot = None;
return; return;
}; };
// Add one because the first item is consumed above. let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
let selected_layers_count = layers.count() + 1; self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
if selected_layers_count == 1 {
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
self.normalized_pivot = normalized_pivot;
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
} else {
// If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
let pivot = xy_summation / selected_layers_count as f64;
self.pivot = Some(pivot);
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.normalized_pivot = (pivot - min) / (max - min);
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
}
}
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
if !overlay_context.visibility_settings.pivot() {
self.active = false;
return;
} else {
self.active = true;
}
self.recalculate_pivot(document);
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, data.0);
}
} }
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas). /// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
pub fn should_refresh_pivot_position(&mut self) -> bool { pub fn should_refresh_pivot_position(&mut self) -> bool {
if !self.active {
return false;
}
let new = self.to_pivot_position(); let new = self.to_pivot_position();
let should_refresh = new != self.old_pivot_position; let should_refresh = new != self.old_pivot_position;
self.old_pivot_position = new; self.old_pivot_position = new;
@@ -118,37 +254,24 @@ impl Pivot {
self.normalized_pivot.into() self.normalized_pivot.into()
} }
/// Sets the viewport position of the pivot for all selected layers. /// Sets the viewport position of the pivot.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn set_viewport_position(&mut self, position: DVec2) {
if !self.active { if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {
return; return;
} };
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);
let transform = Self::get_layer_pivot_transform(layer, document); self.pivot = Some(position);
// Only update the pivot when computed position is finite.
if transform.matrix2.determinant().abs() <= f64::EPSILON {
return;
};
let pivot = transform.inverse().transform_point2(position);
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
}
} }
/// Set the pivot using the normalized transform that is set above. /// Set the pivot using a normalized position.
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn set_normalized_position(&mut self, position: DVec2) {
if !self.active { self.normalized_pivot = position;
return; self.pivot = Some(self.transform_from_normalized.transform_point2(position));
}
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
} }
/// Answers if the pointer is currently positioned over the pivot. /// Answers if the pointer is currently positioned over the pivot.
pub fn is_over(&self, mouse: DVec2) -> bool { pub fn is_over(&self, mouse: DVec2) -> bool {
if !self.active {
return false;
}
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
} }
} }
@@ -212,15 +212,15 @@ impl ClosestSegment {
self.bezier_point_to_viewport self.bezier_point_to_viewport
} }
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 { pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t)); let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
transform.transform_point2(bezier_point) transform.transform_point2(bezier_point)
} }
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position. /// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) { pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position); let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.); let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
@@ -239,9 +239,9 @@ impl ClosestSegment {
tolerance.powi(2) < self.distance_squared(mouse_position) tolerance.powi(2) < self.distance_squared(mouse_position)
} }
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) { pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {
// Transform to viewport space // Transform to viewport space
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
// Split the Bezier at the parameter `t` // Split the Bezier at the parameter `t`
let [first, second] = self.bezier.split(TValue::Parametric(self.t)); let [first, second] = self.bezier.split(TValue::Parametric(self.t));
@@ -307,7 +307,7 @@ impl ClosestSegment {
} }
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 { pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) { let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {
(handle1 - handle2).try_normalize() (handle1 - handle2).try_normalize()
} else { } else {
let [first_point, last_point] = self.points(); let [first_point, last_point] = self.points();
@@ -339,7 +339,7 @@ impl ClosestSegment {
break_colinear_molding: bool, break_colinear_molding: bool,
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>, temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
) -> Option<[Option<HandleId>; 2]> { ) -> Option<[Option<HandleId>; 2]> {
let transform = document.metadata().transform_to_viewport(self.layer); let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
let start = self.bezier.start; let start = self.bezier.start;
let end = self.bezier.end; let end = self.bezier.end;
@@ -507,7 +507,7 @@ impl ShapeState {
continue; continue;
}; };
let to_document = document.metadata().transform_to_document(*layer); let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
for &selected in &state.selected_points { for &selected in &state.selected_points {
let source = match selected { let source = match selected {
@@ -564,7 +564,11 @@ impl ShapeState {
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
// Offset to snap the selected point to the cursor // Offset to snap the selected point to the cursor
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); let offset = mouse_position
- network_interface
.document_metadata()
.transform_to_viewport_if_feeds(layer, network_interface)
.transform_point2(point_position);
// This is selecting the manipulator only for now, next to generalize to points // This is selecting the manipulator only for now, next to generalize to points
@@ -621,7 +625,11 @@ impl ShapeState {
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
// Offset to snap the selected point to the cursor // Offset to snap the selected point to the cursor
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); let offset = mouse_position
- network_interface
.document_metadata()
.transform_to_viewport_if_feeds(layer, network_interface)
.transform_point2(point_position);
// Gather current selection information // Gather current selection information
let points = self let points = self
@@ -653,7 +661,7 @@ impl ShapeState {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return; return;
}; };
let to_viewport = document.metadata().transform_to_viewport(layer); let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let layer_mouse = to_viewport.inverse().transform_point2(mouse); let layer_mouse = to_viewport.inverse().transform_point2(mouse);
let state = self.selected_shape_state.entry(layer).or_default(); let state = self.selected_shape_state.entry(layer).or_default();
@@ -875,7 +883,7 @@ impl ShapeState {
} }
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let transform = network_interface.document_metadata().transform_to_document(layer).inverse(); let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
let position = transform.transform_point2(new_position); let position = transform.transform_point2(new_position);
let current_position = point.get_position(&vector_data)?; let current_position = point.get_position(&vector_data)?;
let delta = position - current_position; let delta = position - current_position;
@@ -1026,7 +1034,7 @@ impl ShapeState {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue; continue;
}; };
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
for &point in layer_state.selected_points.iter() { for &point in layer_state.selected_points.iter() {
let Some(handles) = point.get_handle_pair(&vector_data) else { continue }; let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
@@ -1116,8 +1124,8 @@ impl ShapeState {
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer)); let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer); let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let transform_to_document_space = document.metadata().transform_to_document(layer); let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let delta_transform = if in_viewport_space { let delta_transform = if in_viewport_space {
transform_to_viewport_space transform_to_viewport_space
} else { } else {
@@ -1210,7 +1218,7 @@ impl ShapeState {
.iter() .iter()
.filter_map(|(&layer, state)| { .filter_map(|(&layer, state)| {
let vector_data = document.network_interface.compute_modified_vector(layer)?; let vector_data = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let opposing_handle_lengths = vector_data let opposing_handle_lengths = vector_data
.colinear_manipulators .colinear_manipulators
.iter() .iter()
@@ -1575,7 +1583,7 @@ impl ShapeState {
let mut manipulator_point = None; let mut manipulator_point = None;
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let viewspace = network_interface.document_metadata().transform_to_viewport(layer); let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
// Handles // Handles
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() { for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
@@ -1611,7 +1619,7 @@ impl ShapeState {
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID. /// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> { fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let layer_pos = transform.inverse().transform_point2(position); let layer_pos = transform.inverse().transform_point2(position);
let tolerance = tolerance + 0.5; let tolerance = tolerance + 0.5;
@@ -1785,7 +1793,7 @@ impl ShapeState {
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool { pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer| { let mut process_layer = |layer| {
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer); let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let mut result = None; let mut result = None;
let mut closest_distance_squared = tolerance * tolerance; let mut closest_distance_squared = tolerance * tolerance;
@@ -1889,7 +1897,7 @@ impl ShapeState {
let vector_data = network_interface.compute_modified_vector(layer); let vector_data = network_interface.compute_modified_vector(layer);
let Some(vector_data) = vector_data else { continue }; let Some(vector_data) = vector_data else { continue };
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
@@ -77,7 +77,7 @@ mod test_ellipse {
layers layers
.filter_map(|layer| { .filter_map(|layer| {
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
Some(ResolvedEllipse { Some(ResolvedEllipse {
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(), radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(), radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
@@ -393,6 +393,7 @@ pub fn transforming_transform_cage(
input: &InputPreprocessorMessageHandler, input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
layers_dragging: &mut Vec<LayerNodeIdentifier>, layers_dragging: &mut Vec<LayerNodeIdentifier>,
center_of_transformation: Option<DVec2>,
) -> (bool, bool, bool) { ) -> (bool, bool, bool) {
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| { let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
let edges = bounding_box.check_selected_edges(input.mouse.position); let edges = bounding_box.check_selected_edges(input.mouse.position);
@@ -429,17 +430,12 @@ pub fn transforming_transform_cage(
} }
}); });
let mut selected = Selected::new( bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
&mut bounds.original_transforms, document
&mut bounds.center_of_transformation, .network_interface
layers_dragging, .selected_nodes()
responses, .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
&document.network_interface, });
None,
&ToolType::Select,
None,
);
bounds.center_of_transformation = selected.mean_average_of_pivots();
// Check if we're hovering over a skew triangle // Check if we're hovering over a skew triangle
let edges = bounds.check_selected_edges(input.mouse.position); let edges = bounds.check_selected_edges(input.mouse.position);
@@ -469,18 +465,12 @@ pub fn transforming_transform_cage(
} }
}); });
let mut selected = Selected::new( bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
&mut bounds.original_transforms, document
&mut bounds.center_of_transformation, .network_interface
&selected, .selected_nodes()
responses, .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
&document.network_interface, });
None,
&ToolType::Select,
None,
);
bounds.center_of_transformation = selected.mean_average_of_pivots();
} }
*layers_dragging = selected; *layers_dragging = selected;
@@ -181,6 +181,11 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
send: Box::new(TransformLayerMessage::SelectionChanged.into()), send: Box::new(TransformLayerMessage::SelectionChanged.into()),
}); });
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
send: Box::new(SelectToolMessage::SyncHistory.into()),
});
self.tool_is_active = true; self.tool_is_active = true;
let tool_data = &mut self.tool_state.tool_data; let tool_data = &mut self.tool_state.tool_data;
@@ -567,7 +567,7 @@ mod test_artboard {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e), Err(e) => panic!("Failed to evaluate graph: {}", e),
}; };
instrumented.grab_all_input::<graphene_std::append_artboard::ArtboardInput>(&editor.runtime).collect() instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
} }
#[tokio::test] #[tokio::test]
@@ -11,13 +11,15 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::portfolio::document::utility_types::transformation::Axis;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
use crate::messages::tool::common_functionality::shape_editor::{ use crate::messages::tool::common_functionality::shape_editor::{
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
}; };
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
use bezier_rs::{Bezier, TValue}; use bezier_rs::{Bezier, BezierHandles, TValue};
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::transform::ReferencePoint;
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData}; use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
use std::vec; use std::vec;
@@ -106,6 +108,9 @@ pub enum PathToolMessage {
SelectedPointYChanged { SelectedPointYChanged {
new_y: f64, new_y: f64,
}, },
SetPivot {
position: ReferencePoint,
},
SwapSelectedHandles, SwapSelectedHandles,
UpdateOptions(PathOptionsUpdate), UpdateOptions(PathOptionsUpdate),
UpdateSelectedPointsStatus { UpdateSelectedPointsStatus {
@@ -141,6 +146,9 @@ pub enum PathOptionsUpdate {
OverlayModeType(PathOverlayMode), OverlayModeType(PathOverlayMode),
PointEditingMode { enabled: bool }, PointEditingMode { enabled: bool },
SegmentEditingMode { enabled: bool }, SegmentEditingMode { enabled: bool },
PivotGizmoType(PivotGizmoType),
TogglePivotGizmoType(bool),
TogglePivotPinned,
} }
impl ToolMetadata for PathTool { impl ToolMetadata for PathTool {
@@ -255,6 +263,20 @@ impl LayoutHolder for PathTool {
.selected_index(Some(self.options.path_overlay_mode as u32)) .selected_index(Some(self.options.path_overlay_mode as u32))
.widget_holder(); .widget_holder();
let [_checkbox, _dropdown] = {
let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);
[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]
};
let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();
let _pivot_reference = pivot_reference_point_widget(
has_something || !self.tool_data.pivot_gizmo.state.is_pivot(),
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
PivotToolSource::Path,
);
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
x_location, x_location,
@@ -268,8 +290,16 @@ impl LayoutHolder for PathTool {
point_editing_mode, point_editing_mode,
related_seperator.clone(), related_seperator.clone(),
segment_editing_mode, segment_editing_mode,
unrelated_seperator, unrelated_seperator.clone(),
path_overlay_mode_widget, path_overlay_mode_widget,
unrelated_seperator.clone(),
// checkbox.clone(),
// related_seperator.clone(),
// dropdown.clone(),
// unrelated_seperator,
// pivot_reference,
// related_seperator.clone(),
// pin_pivot,
], ],
}])) }]))
} }
@@ -293,6 +323,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
self.options.path_editing_mode.segment_editing_mode = enabled; self.options.path_editing_mode.segment_editing_mode = enabled;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
PathOptionsUpdate::PivotGizmoType(gizmo_type) => {
if !self.tool_data.pivot_gizmo.state.disabled {
self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;
responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
}
PathOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
PathOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
}, },
ToolMessage::Path(PathToolMessage::ClosePath) => { ToolMessage::Path(PathToolMessage::ClosePath) => {
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
@@ -443,6 +496,8 @@ struct PathToolData {
last_click_time: u64, last_click_time: u64,
dragging_state: DraggingState, dragging_state: DraggingState,
angle: f64, angle: f64,
pivot_gizmo: PivotGizmo,
ordered_points: Vec<ManipulatorPointId>,
opposite_handle_position: Option<DVec2>, opposite_handle_position: Option<DVec2>,
last_clicked_point_was_selected: bool, last_clicked_point_was_selected: bool,
last_clicked_segment_was_selected: bool, last_clicked_segment_was_selected: bool,
@@ -674,13 +729,15 @@ impl PathToolData {
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
PathToolFsmState::Dragging(self.dragging_state) PathToolFsmState::Dragging(self.dragging_state)
} else { } else {
let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment()); let start_pos = segment.bezier().start;
let handle2 = ManipulatorPointId::EndHandle(segment.segment()); let end_pos = segment.bezier().end;
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) { let [pos1, pos2] = match segment.bezier().handles {
self.molding_info = Some((pos1, pos2)) BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
} BezierHandles::Quadratic { handle } => [handle, end_pos],
} BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
};
self.molding_info = Some((pos1, pos2));
PathToolFsmState::Dragging(self.dragging_state) PathToolFsmState::Dragging(self.dragging_state)
} }
} }
@@ -716,7 +773,7 @@ impl PathToolData {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue; continue;
}; };
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
for point in state.selected_points() { for point in state.selected_points() {
@@ -826,7 +883,7 @@ impl PathToolData {
let selected_handle = selection.selected_points().next()?.as_handle()?; let selected_handle = selection.selected_points().next()?.as_handle()?;
let handle_id = selected_handle.to_manipulator_point(); let handle_id = selected_handle.to_manipulator_point();
let layer_to_document = document.metadata().transform_to_document(*layer); let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
let vector_data = document.network_interface.compute_modified_vector(*layer)?; let vector_data = document.network_interface.compute_modified_vector(*layer)?;
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?; let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
@@ -868,7 +925,7 @@ impl PathToolData {
let anchor = handle_id.get_anchor(&vector_data); let anchor = handle_id.get_anchor(&vector_data);
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data); let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
let layer_to_document = document.metadata().transform_to_document(*layer); let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
self.adjacent_anchor_offset = handle_id self.adjacent_anchor_offset = handle_id
.get_anchor_position(&vector_data) .get_anchor_position(&vector_data)
@@ -1015,7 +1072,7 @@ impl PathToolData {
} }
// If already hovering on a segment, then recalculate its closest point // If already hovering on a segment, then recalculate its closest point
else if let Some(closest_segment) = &mut self.segment { else if let Some(closest_segment) = &mut self.segment {
closest_segment.update_closest_point(document.metadata(), position); closest_segment.update_closest_point(document.metadata(), &document.network_interface, position);
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) { if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
self.segment = None; self.segment = None;
@@ -1082,7 +1139,7 @@ impl PathToolData {
let layer = sliding_point_info.layer; let layer = sliding_point_info.layer;
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return }; let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let layer_pos = transform.inverse().transform_point2(target_position); let layer_pos = transform.inverse().transform_point2(target_position);
let segments = sliding_point_info.connected_segments; let segments = sliding_point_info.connected_segments;
@@ -1313,6 +1370,16 @@ impl PathToolData {
} }
} }
} }
fn pivot_gizmo(&self) -> PivotGizmo {
self.pivot_gizmo.clone()
}
fn sync_history(&mut self, points: &[ManipulatorPointId]) {
self.ordered_points.retain(|layer| points.contains(layer));
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
self.pivot_gizmo.point = self.ordered_points.last().copied()
}
} }
impl Fsm for PathToolFsmState { impl Fsm for PathToolFsmState {
@@ -1325,6 +1392,10 @@ impl Fsm for PathToolFsmState {
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options); update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
let ToolMessage::Path(event) = event else { return self }; let ToolMessage::Path(event) = event else { return self };
// TODO(mTvare6): Remove once gizmos are implemented for path_tool
tool_data.pivot_gizmo.state.disabled = true;
match (self, event) { match (self, event) {
(_, PathToolMessage::SelectionChanged) => { (_, PathToolMessage::SelectionChanged) => {
// Set the newly targeted layers to visible // Set the newly targeted layers to visible
@@ -1341,6 +1412,9 @@ impl Fsm for PathToolFsmState {
shape_editor.update_selected_anchors_status(display_anchors); shape_editor.update_selected_anchors_status(display_anchors);
shape_editor.update_selected_handles_status(display_handles); shape_editor.update_selected_handles_status(display_handles);
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
tool_data.sync_history(&new_points);
self self
} }
(_, PathToolMessage::Overlays(mut overlay_context)) => { (_, PathToolMessage::Overlays(mut overlay_context)) => {
@@ -1410,7 +1484,7 @@ impl Fsm for PathToolFsmState {
if let Some(closest_segment) = &tool_data.segment { if let Some(closest_segment) = &tool_data.segment {
if tool_options.path_editing_mode.segment_editing_mode { if tool_options.path_editing_mode.segment_editing_mode {
let transform = document.metadata().transform_to_viewport(closest_segment.layer()); let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform); overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
@@ -1428,7 +1502,7 @@ impl Fsm for PathToolFsmState {
} }
} else { } else {
let perp = closest_segment.calculate_perp(document); let perp = closest_segment.calculate_perp(document);
let point = closest_segment.closest_point(document.metadata()); let point = closest_segment.closest_point(document.metadata(), &document.network_interface);
// Draw an X on the segment // Draw an X on the segment
if tool_data.delete_segment_pressed { if tool_data.delete_segment_pressed {
@@ -1708,14 +1782,8 @@ impl Fsm for PathToolFsmState {
} }
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => { (PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize); tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() { tool_data.adjacent_anchor_offset = None;
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
}
if tool_data.adjacent_anchor_offset.is_some() {
tool_data.adjacent_anchor_offset = None;
}
tool_data.stored_selection = None; tool_data.stored_selection = None;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@@ -2206,6 +2274,18 @@ impl Fsm for PathToolFsmState {
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
PathToolFsmState::Ready PathToolFsmState::Ready
} }
(_, PathToolMessage::SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
let position: Option<DVec2> = position.into();
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self
}
(_, _) => PathToolFsmState::Ready, (_, _) => PathToolFsmState::Ready,
} }
} }
@@ -2278,7 +2358,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
return SelectionStatus::None; return SelectionStatus::None;
}; };
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position); let coordinates = network_interface
.document_metadata()
.transform_to_document_if_feeds(layer, network_interface)
.transform_point2(local_position);
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }; let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
return SelectionStatus::One(SingleSelectedPoint { return SelectionStatus::One(SingleSelectedPoint {
@@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
use crate::messages::tool::common_functionality::measure; use crate::messages::tool::common_functionality::measure;
use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
@@ -43,6 +44,9 @@ pub struct SelectOptions {
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SelectOptionsUpdate { pub enum SelectOptionsUpdate {
NestedSelectionBehavior(NestedSelectionBehavior), NestedSelectionBehavior(NestedSelectionBehavior),
PivotGizmoType(PivotGizmoType),
TogglePivotGizmoType(bool),
TogglePivotPinned,
} }
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -95,6 +99,14 @@ pub enum SelectToolMessage {
SetPivot { SetPivot {
position: ReferencePoint, position: ReferencePoint,
}, },
SyncHistory,
ShiftSelectedNodes {
offset: DVec2,
},
PivotShift {
offset: Option<DVec2>,
flush: bool,
},
} }
impl ToolMetadata for SelectTool { impl ToolMetadata for SelectTool {
@@ -122,14 +134,12 @@ impl SelectTool {
DropdownInput::new(vec![layer_selection_behavior_entries]) DropdownInput::new(vec![layer_selection_behavior_entries])
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32)) .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") .tooltip(
.widget_holder() "Selection Mode\n\
} \n\
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder { Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
ReferencePointInput::new(self.tool_data.pivot.to_pivot_position()) )
.on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into())
.disabled(disabled)
.widget_holder() .widget_holder()
} }
@@ -178,7 +188,7 @@ impl SelectTool {
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> { fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list(); let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| { list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
let mut tooltip = info.label.to_string(); let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring.as_deref() { if let Some(doc) = info.docstring.as_deref() {
tooltip.push_str("\n\n"); tooltip.push_str("\n\n");
@@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool {
// Select mode (Deep/Shallow) // Select mode (Deep/Shallow)
widgets.push(self.deep_selection_widget()); widgets.push(self.deep_selection_widget());
// Pivot // Pivot gizmo type (checkbox + dropdown for pivot/origin)
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0)); widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
// Nine-position reference point widget
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(pivot_reference_point_widget(
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
PivotToolSource::Select,
));
// Pivot pin button
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
let pin_active = self.tool_data.pivot_gizmo.pin_active();
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
if pin_active || pin_enabled {
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
}
}
// Align // Align
let disabled = self.tool_data.selected_layers_count < 2; let disabled = self.tool_data.selected_layers_count < 2;
@@ -244,14 +274,43 @@ impl LayoutHolder for SelectTool {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message { let mut redraw_reference_pivot = false;
self.tool_data.nested_selection_behavior = nested_selection_behavior;
responses.add(ToolMessage::UpdateHints); if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
match option_update {
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
responses.add(ToolMessage::UpdateHints);
}
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
if !self.tool_data.pivot_gizmo.state.disabled {
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
}
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
SelectOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
}
} }
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed { if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
self.send_layout(responses, LayoutTarget::ToolOptions); self.send_layout(responses, LayoutTarget::ToolOptions);
self.tool_data.selected_layers_changed = false; self.tool_data.selected_layers_changed = false;
@@ -323,7 +382,8 @@ struct SelectToolData {
drag_current: ViewportPosition, drag_current: ViewportPosition,
lasso_polygon: Vec<ViewportPosition>, lasso_polygon: Vec<ViewportPosition>,
selection_mode: Option<SelectionMode>, selection_mode: Option<SelectionMode>,
layers_dragging: Vec<LayerNodeIdentifier>, layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
layer_selected_on_start: Option<LayerNodeIdentifier>, layer_selected_on_start: Option<LayerNodeIdentifier>,
select_single_layer: Option<LayerNodeIdentifier>, select_single_layer: Option<LayerNodeIdentifier>,
axis_align: bool, axis_align: bool,
@@ -331,7 +391,9 @@ struct SelectToolData {
bounding_box_manager: Option<BoundingBoxManager>, bounding_box_manager: Option<BoundingBoxManager>,
snap_manager: SnapManager, snap_manager: SnapManager,
cursor: MouseCursorIcon, cursor: MouseCursorIcon,
pivot: Pivot, pivot_gizmo: PivotGizmo,
pivot_gizmo_start: Option<DVec2>,
pivot_gizmo_shift: Option<DVec2>,
compass_rose: CompassRose, compass_rose: CompassRose,
line_center: DVec2, line_center: DVec2,
skew_edge: EdgeBool, skew_edge: EdgeBool,
@@ -497,6 +559,24 @@ impl SelectToolData {
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original; self.layers_dragging = original;
} }
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
match self.pivot_gizmo.state.gizmo_type {
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
_ => None,
}
}
fn pivot_gizmo(&self) -> PivotGizmo {
self.pivot_gizmo.clone()
}
fn sync_history(&mut self, document: &DocumentMessageHandler) {
let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
self.ordered_layers.retain(|layer| layers.contains(layer));
self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));
self.pivot_gizmo.layer = self.ordered_layers.last().copied()
}
} }
impl Fsm for SelectToolFsmState { impl Fsm for SelectToolFsmState {
@@ -710,8 +790,63 @@ impl Fsm for SelectToolFsmState {
.flatten() .flatten()
}); });
// Update pivot let mut active_origin = None;
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); let mut origin_angle = 0.;
if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {
let get_angle = |layer: LayerNodeIdentifier| -> f64 {
let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;
(bounds.top_left() - bounds.top_right()).to_angle()
};
if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {
let mut count = 0_usize;
let sum: f64 = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(get_angle)
.inspect(|_| count += 1)
.sum();
if count > 0 {
origin_angle = sum / count as f64;
}
} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {
origin_angle = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)
.iter()
.map(|&layer| get_angle(layer))
.sum();
}
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);
if Some(layer) == tool_data.pivot_gizmo.layer {
active_origin = Some(origin);
continue;
}
overlay_context.dowel_pin(origin, origin_angle, None);
}
}
if let Some(origin) = active_origin {
overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));
}
let has_layers = document.network_interface.selected_nodes().has_selected_nodes();
let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
if let Some(pivot) = pivot {
let offset = tool_data
.pivot_gizmo_start
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
.unwrap_or_default();
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
overlay_context.pivot(pivot + offset + shift, angle);
}
// Update compass rose // Update compass rose
if overlay_context.visibility_settings.compass_rose() { if overlay_context.visibility_settings.compass_rose() {
@@ -837,6 +972,7 @@ impl Fsm for SelectToolFsmState {
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color), (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
} }
} }
self self
} }
(_, SelectToolMessage::EditLayer) => { (_, SelectToolMessage::EditLayer) => {
@@ -868,7 +1004,8 @@ impl Fsm for SelectToolFsmState {
let intersection_list = document.click_list(input).collect::<Vec<_>>(); let intersection_list = document.click_list(input).collect::<Vec<_>>();
let intersection = document.find_deepest(&intersection_list); let intersection = document.find_deepest(&intersection_list);
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); let position = tool_data.pivot_gizmo().position(document);
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));
// 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.
// If the user is dragging the rotate trigger, go into RotatingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode.
@@ -883,20 +1020,17 @@ impl Fsm for SelectToolFsmState {
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
let state = if is_over_pivot let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {
// Dragging the pivot
{
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); // tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
SelectToolFsmState::DraggingPivot state
} }
// Dragging one (or two, forming a corner) of the transform cage bounding box edges // Dragging one (or two, forming a corner) of the transform cage bounding box edges
else if resize { else if resize {
@@ -917,12 +1051,14 @@ impl Fsm for SelectToolFsmState {
} }
tool_data.layers_dragging = selected; tool_data.layers_dragging = selected;
tool_data.get_snap_candidates(document, input); tool_data.get_snap_candidates(document, input);
let (axis, using_compass) = { let (axis, using_compass) = {
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
(axis_state.unwrap_or_default(), axis_state.is_some()) (axis_state.unwrap_or_default(), axis_state.is_some())
}; };
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging { SelectToolFsmState::Dragging {
axis, axis,
using_compass, using_compass,
@@ -941,6 +1077,12 @@ impl Fsm for SelectToolFsmState {
let extend = input.keyboard.key(extend_selection); let extend = input.keyboard.key(extend_selection);
if !extend && !input.keyboard.key(remove_from_selection) { if !extend && !input.keyboard.key(remove_from_selection) {
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
if !tool_data.pivot_gizmo.pivot.pinned {
let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;
responses.add(SelectToolMessage::SetPivot { position });
}
tool_data.layers_dragging.clear(); tool_data.layers_dragging.clear();
} }
@@ -955,6 +1097,9 @@ impl Fsm for SelectToolFsmState {
tool_data.get_snap_candidates(document, input); tool_data.get_snap_candidates(document, input);
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging { SelectToolFsmState::Dragging {
axis: Axis::None, axis: Axis::None,
using_compass: false, using_compass: false,
@@ -1098,7 +1243,10 @@ impl Fsm for SelectToolFsmState {
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position; let snapped_mouse_position = mouse_position;
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
responses.add(NodeGraphMessage::RunDocumentGraph);
// Auto-panning // Auto-panning
let messages = [ let messages = [
@@ -1143,7 +1291,7 @@ impl Fsm for SelectToolFsmState {
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
// Dragging the pivot overrules the other operations // Dragging the pivot overrules the other operations
if tool_data.pivot.is_over(input.mouse.position) { if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {
cursor = MouseCursorIcon::Move; cursor = MouseCursorIcon::Move;
} }
@@ -1283,20 +1431,32 @@ impl Fsm for SelectToolFsmState {
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
tool_data.select_single_layer = None; tool_data.select_single_layer = None;
if let Some(start) = tool_data.pivot_gizmo_start {
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset;
}
}
tool_data.pivot_gizmo_start = None;
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
let selection = tool_data.nested_selection_behavior; let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection } SelectToolFsmState::Ready { selection }
} }
( (
SelectToolFsmState::ResizingBounds SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
| SelectToolFsmState::SkewingBounds { .. }
| SelectToolFsmState::RotatingBounds
| SelectToolFsmState::Dragging { .. }
| SelectToolFsmState::DraggingPivot,
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
) => { ) => {
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(response); responses.add(response);
tool_data.axis_align = false; tool_data.axis_align = false;
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
@@ -1432,8 +1592,48 @@ impl Fsm for SelectToolFsmState {
(_, SelectToolMessage::SetPivot { position }) => { (_, SelectToolMessage::SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
tool_data.pivot_gizmo.pivot.pinned = false;
let pos: Option<DVec2> = position.into(); let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses);
tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self
}
(_, SelectToolMessage::SyncHistory) => {
tool_data.sync_history(document);
self
}
(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {
let offset = document.metadata().document_to_viewport.transform_vector2(offset);
if tool_data.pivot_gizmo.pivot_disconnected() {
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset;
}
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
}
self
}
(_, SelectToolMessage::PivotShift { offset, flush }) => {
if flush {
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
return self;
}
if tool_data.pivot_gizmo.pivot_disconnected() {
tool_data.pivot_gizmo_shift = offset;
}
self self
} }
@@ -1658,6 +1858,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
.next() .next()
.expect("ROOT_PARENT should have a layer child when clicking"), .expect("ROOT_PARENT should have a layer child when clicking"),
); );
if !remove { if !remove {
tool_data.layers_dragging.extend(vec![layer]); tool_data.layers_dragging.extend(vec![layer]);
} else { } else {
@@ -557,7 +557,7 @@ impl Fsm for ShapeToolFsmState {
} }
} }
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
if !input.keyboard.key(Key::Control) { if !input.keyboard.key(Key::Control) {
match (resize, rotate, skew) { match (resize, rotate, skew) {
@@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
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::{self, is_layer_fed_by_node_of_name}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
use crate::messages::tool::common_functionality::pivot::Pivot;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
@@ -283,7 +282,6 @@ struct TextToolData {
// Since the overlays must be drawn without knowledge of the inputs // Since the overlays must be drawn without knowledge of the inputs
cached_resize_bounds: [DVec2; 2], cached_resize_bounds: [DVec2; 2],
bounding_box_manager: Option<BoundingBoxManager>, bounding_box_manager: Option<BoundingBoxManager>,
pivot: Pivot,
snap_candidates: Vec<SnapCandidatePoint>, snap_candidates: Vec<SnapCandidatePoint>,
// TODO: Handle multiple layers in the future // TODO: Handle multiple layers in the future
layer_dragging: Option<ResizingLayer>, layer_dragging: Option<ResizingLayer>,
@@ -526,7 +524,6 @@ impl Fsm for TextToolFsmState {
} }
bounding_box_manager.render_overlays(&mut overlay_context, false); bounding_box_manager.render_overlays(&mut overlay_context, false);
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
} }
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
@@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
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::transformation::TransformType; use crate::messages::portfolio::document::utility_types::transformation::TransformType;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::pivot::PivotGizmo;
use glam::DVec2; use glam::DVec2;
#[impl_message(Message, ToolMessage, TransformLayer)] #[impl_message(Message, ToolMessage, TransformLayer)]
@@ -29,4 +30,5 @@ pub enum TransformLayerMessage {
TypeDecimalPoint, TypeDecimalPoint,
TypeDigit { digit: u8 }, TypeDigit { digit: u8 },
TypeNegate, TypeNegate,
SetPivotGizmo { pivot_gizmo: PivotGizmo },
} }
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::misc::PTZ; use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing}; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::{ToolData, ToolType}; use crate::messages::tool::utility_types::{ToolData, ToolType};
@@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler {
start_mouse: ViewportPosition, start_mouse: ViewportPosition,
original_transforms: OriginalTransforms, original_transforms: OriginalTransforms,
pivot_gizmo: PivotGizmo,
pivot: ViewportPosition, pivot: ViewportPosition,
path_bounds: Option<[DVec2; 2]>,
local_pivot: DocumentPosition, local_pivot: DocumentPosition,
local_mouse_start: DocumentPosition, local_mouse_start: DocumentPosition,
grab_target: DocumentPosition, grab_target: DocumentPosition,
@@ -61,27 +65,64 @@ impl TransformLayerMessageHandler {
} }
} }
fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>) -> Option<(DVec2, DVec2)> { fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
gizmo: &mut PivotGizmo,
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
let average_position = || {
let mut point_count = 0_usize;
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
};
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
if let Some([mut min, mut max]) = acc {
min.x = min.x.min(point.x);
min.y = min.y.min(point.y);
max.x = max.x.max(point.x);
max.y = max.y.max(point.y);
Some([min, max])
} else {
Some([point, point])
}
});
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
let position = || {
(if !gizmo.state.disabled {
match gizmo.state.gizmo_type {
PivotGizmoType::Average => None,
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
PivotGizmoType::Pivot => gizmo.pivot.pivot,
}
} else {
None
})
.unwrap_or_else(average_position)
};
let [point] = selected_points.as_slice() else { let [point] = selected_points.as_slice() else {
// Handle the case where there are multiple points // Handle the case where there are multiple points
let mut point_count = 0; let position = position();
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64; return (Some((position, position)), bounds);
return Some((average_position, average_position));
}; };
match point { match point {
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
// Get the anchor position and transform it to the pivot // Get the anchor position and transform it to the pivot
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?; let (Some(pivot_position), Some(position)) = (
let target = viewspace.transform_point2(point.get_position(vector_data)?); point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
Some((pivot_pos, target)) point.get_position(vector_data),
) else {
return (None, None);
};
let target = viewspace.transform_point2(position);
(Some((pivot_position, target)), None)
} }
_ => { _ => {
// Calculate the average position of all selected points // Calculate the average position of all selected points
let mut point_count = 0; let position = position();
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64; (Some((position, position)), bounds)
Some((average_position, average_position))
} }
} }
} }
@@ -177,18 +218,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
} }
if !using_path_tool { if !using_path_tool {
*selected.pivot = selected.mean_average_of_pivots(); self.pivot_gizmo.recalculate_transform(document);
*selected.pivot = self.pivot_gizmo.position(document);
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); self.grab_target = self.local_pivot;
} }
// Here vector data from all layers is not considered which can be a problem in pivot calculation // Here vector data from all layers is not considered which can be a problem in pivot calculation
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
*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 selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>(); let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>(); let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() { for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
@@ -201,8 +241,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let affected_point_refs = affected_points.iter().collect(); let affected_point_refs = affected_points.iter().collect();
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) { if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
document,
&affected_point_refs,
&vector_data,
viewspace,
|point: &ManipulatorPointId| get_location(&point),
&mut self.pivot_gizmo,
) {
*selected.pivot = new_pivot; *selected.pivot = new_pivot;
self.path_bounds = bounds;
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot); self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target); self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
@@ -228,116 +276,115 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
return; return;
} }
for layer in document.metadata().all_layers() { let viewport_box = input.viewport_bounds.size();
if !document.network_interface.is_artboard(&layer.to_node(), &[]) { let axis_constraint = self.transform_operation.axis_constraint();
continue;
};
let viewport_box = input.viewport_bounds.size(); let format_rounded = |value: f64, precision: usize| {
let axis_constraint = self.transform_operation.axis_constraint(); if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
} else {
self.typing.string.clone()
}
};
let format_rounded = |value: f64, precision: usize| { // TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { // responses.add(TransformLayerMessage::PointerMove {
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() // slow_key: SLOW_KEY,
// increments_key: INCREMENTS_KEY,
// });
match self.transform_operation {
TransformOperation::None => (),
TransformOperation::Grabbing(translation) => {
let translation = translation.to_dvec(self.initial_transform, self.increments);
let viewport_translate = document_to_viewport.transform_vector2(translation);
let pivot = document_to_viewport.transform_point2(self.grab_target);
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
responses.add(SelectToolMessage::PivotShift {
offset: Some(viewport_translate),
flush: false,
});
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
}
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
let x_parameter = viewport_translate.x.clamp(-1., 1.);
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
}
}
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
overlay_context.line(quad[1], quad[2], None, None);
overlay_context.line(quad[3], quad[2], None, None);
}
}
TransformOperation::Scaling(scale) => {
let scale = scale.to_f64(self.increments);
let text = format!("{}x", format_rounded(scale, 3));
let pivot = document_to_viewport.transform_point2(self.local_pivot);
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
let local_edge = start_mouse - pivot;
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
let boundary_point = pivot + local_edge * scale.min(1.);
let end_point = pivot + local_edge * scale.max(1.);
if scale > 0. {
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
}
overlay_context.line(boundary_point, end_point, None, None);
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
}
TransformOperation::Rotating(rotation) => {
let angle = rotation.to_f64(self.increments);
let pivot = document_to_viewport.transform_point2(self.local_pivot);
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
let offset_angle = if self.grs_pen_handle {
self.handle - self.last_point
} else if using_path_tool {
start_mouse - pivot
} else { } else {
self.typing.string.clone() self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
} };
}; let tilt_offset = document.document_ptz.unmodified_tilt();
let offset_angle = offset_angle.to_angle() + tilt_offset;
// TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops let width = viewport_box.max_element();
// responses.add(TransformLayerMessage::PointerMove { let radius = start_mouse.distance(pivot);
// slow_key: SLOW_KEY, let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
// increments_key: INCREMENTS_KEY, let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
// }); let angle_in_degrees = angle.to_degrees();
let display_angle = if angle_in_degrees.is_sign_positive() {
match self.transform_operation { angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
TransformOperation::None => (), } else if angle_in_degrees.is_sign_negative() {
TransformOperation::Grabbing(translation) => { angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
let translation = translation.to_dvec(self.initial_transform, self.increments); } else {
let viewport_translate = document_to_viewport.transform_vector2(translation); angle_in_degrees
let pivot = document_to_viewport.transform_point2(self.grab_target); };
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; let text = format!("{}°", format_rounded(display_angle, 2));
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); let text_texture_width = overlay_context.get_width(&text) / 2.;
let text_texture_height = 12.;
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; let text_texture_position = DVec2::new(
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); (arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); );
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); let transform = DAffine2::from_translation(text_texture_position + pivot);
} overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
let x_parameter = viewport_translate.x.clamp(-1., 1.);
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
}
}
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
overlay_context.line(quad[1], quad[2], None, None);
overlay_context.line(quad[3], quad[2], None, None);
}
}
TransformOperation::Scaling(scale) => {
let scale = scale.to_f64(self.increments);
let text = format!("{}x", format_rounded(scale, 3));
let pivot = document_to_viewport.transform_point2(self.local_pivot);
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
let local_edge = start_mouse - pivot;
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
let boundary_point = pivot + local_edge * scale.min(1.);
let end_point = pivot + local_edge * scale.max(1.);
if scale > 0. {
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
}
overlay_context.line(boundary_point, end_point, None, None);
let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
}
TransformOperation::Rotating(rotation) => {
let angle = rotation.to_f64(self.increments);
let pivot = document_to_viewport.transform_point2(self.local_pivot);
let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start);
let offset_angle = if self.grs_pen_handle {
self.handle - self.last_point
} else if using_path_tool {
start_mouse - pivot
} else {
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
};
let tilt_offset = document.document_ptz.unmodified_tilt();
let offset_angle = offset_angle.to_angle() + tilt_offset;
let width = viewport_box.max_element();
let radius = start_mouse.distance(pivot);
let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width;
let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width);
let angle_in_degrees = angle.to_degrees();
let display_angle = if angle_in_degrees.is_sign_positive() {
angle_in_degrees - (angle_in_degrees / 360.).floor() * 360.
} else if angle_in_degrees.is_sign_negative() {
angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360.
} else {
angle_in_degrees
};
let text = format!("{}°", format_rounded(display_angle, 2));
let text_texture_width = overlay_context.get_width(&text) / 2.;
let text_texture_height = 12.;
let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle);
let text_texture_position = DVec2::new(
(arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x,
(arc_radius + text_texture_height) * text_angle_on_unit_circle.y,
);
let transform = DAffine2::from_translation(text_texture_position + pivot);
overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle);
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
}
} }
} }
} }
@@ -364,6 +411,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
if final_transform { if final_transform {
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
} }
@@ -487,6 +536,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
} }
TransformLayerMessage::ConstrainX => { TransformLayerMessage::ConstrainX => {
@@ -694,6 +744,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
self.initial_transform, self.initial_transform,
) )
} }
TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {
self.pivot_gizmo = pivot_gizmo;
}
} }
} }
+5 -4
View File
@@ -413,6 +413,7 @@ mod test {
use super::*; use super::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::test_utils::test_prelude::{self, NodeGraphLayer}; use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork; use graph_craft::document::NodeNetwork;
use graphene_std::Context; use graphene_std::Context;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
@@ -422,7 +423,7 @@ mod test {
/// Stores all of the monitor nodes that have been attached to a graph /// Stores all of the monitor nodes that have been attached to a graph
#[derive(Default)] #[derive(Default)]
pub struct Instrumented { pub struct Instrumented {
protonodes_by_name: HashMap<String, Vec<Vec<Vec<NodeId>>>>, protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>, protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
} }
@@ -449,7 +450,7 @@ mod test {
} }
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
path.push(*id); path.push(*id);
self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone()); self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
self.protonodes_by_path.insert(path.clone(), monitor_node_ids); self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
path.pop(); path.pop();
} }
@@ -457,7 +458,7 @@ mod test {
for (input, monitor_id) in monitor_nodes { for (input, monitor_id) in monitor_nodes {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![input], inputs: vec![input],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -495,7 +496,7 @@ mod test {
Input::Result: Send + Sync + Clone + 'static, Input::Result: Send + Sync + Clone + 'static,
{ {
self.protonodes_by_name self.protonodes_by_name
.get(Input::identifier()) .get(&Input::identifier())
.map_or([].as_slice(), |x| x.as_slice()) .map_or([].as_slice(), |x| x.as_slice())
.iter() .iter()
.filter_map(|inputs| inputs.get(Input::INDEX)) .filter_map(|inputs| inputs.get(Input::INDEX))
+3 -3
View File
@@ -1,12 +1,12 @@
use super::*; use super::*;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler; use graph_craft::graphene_compiler::Compiler;
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Context; use graphene_std::Context;
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::instances::Instance; use graphene_std::instances::Instance;
@@ -46,7 +46,7 @@ pub struct NodeRuntime {
inspect_state: Option<InspectState>, inspect_state: Option<InspectState>,
/// Mapping of the fully-qualified node paths to their preprocessor substitutions. /// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<String, DocumentNode>, substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
// TODO: Remove, it doesn't need to be persisted anymore // TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes. /// The current renders of the thumbnails for layer nodes.
@@ -435,7 +435,7 @@ impl InspectState {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
-4
View File
@@ -3,10 +3,6 @@
use axum::routing::get; use axum::routing::get;
use axum::Router; use axum::Router;
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use graphite_editor::application::Editor;
use graphite_editor::messages::prelude::*;
use graphite_editor::node_graph_executor::GraphRuntimeRequest;
use graphite_editor::node_graph_executor::NODE_RUNTIME;
use graphite_editor::node_graph_executor::*; use graphite_editor::node_graph_executor::*;
use std::sync::Mutex; use std::sync::Mutex;
@@ -7,13 +7,14 @@
export let value: string; export let value: string;
export let disabled = false; export let disabled = false;
export let tooltip: string | undefined = undefined;
function setValue(newValue: ReferencePoint) { function setValue(newValue: ReferencePoint) {
dispatch("value", newValue); dispatch("value", newValue);
} }
</script> </script>
<div class="reference-point-input" class:disabled> <div class="reference-point-input" class:disabled title={tooltip}>
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
+3
View File
@@ -1340,6 +1340,9 @@ export class ReferencePointInput extends WidgetProps {
value!: ReferencePoint; value!: ReferencePoint;
disabled!: boolean; disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
} }
// WIDGET // WIDGET
+1 -1
View File
@@ -356,7 +356,7 @@ pub struct ContextImpl<'a> {
} }
impl<'a> ContextImpl<'a> { impl<'a> ContextImpl<'a> {
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
where where
'a: 'f, 'a: 'f,
{ {
+3 -3
View File
@@ -12,7 +12,7 @@ pub mod debug;
pub mod extract_xy; pub mod extract_xy;
pub mod generic; pub mod generic;
pub mod gradient; pub mod gradient;
mod graphic_element; pub mod graphic_element;
pub mod instances; pub mod instances;
pub mod logic; pub mod logic;
pub mod math; pub mod math;
@@ -35,7 +35,7 @@ pub use blending::*;
pub use context::*; pub use context::*;
pub use ctor; pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphic_element::*; pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::MemoHash; pub use memo::MemoHash;
pub use num_traits; pub use num_traits;
pub use raster::Color; pub use raster::Color;
@@ -161,7 +161,7 @@ where
pub trait NodeInputDecleration { pub trait NodeInputDecleration {
const INDEX: usize; const INDEX: usize;
fn identifier() -> &'static str; fn identifier() -> ProtoNodeIdentifier;
type Result; type Result;
} }
+13 -1
View File
@@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend};
use dyn_any::DynFuture; use dyn_any::DynFuture;
use std::future::Future; use std::future::Future;
use std::hash::DefaultHasher; use std::hash::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@@ -49,6 +50,10 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
} }
} }
pub mod memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
}
/// Caches the output of a given Node and acts as a proxy. /// Caches the output of a given Node and acts as a proxy.
/// In contrast to the regular `MemoNode`. This node ignores all input. /// In contrast to the regular `MemoNode`. This node ignores all input.
/// Using this node might result in the document not updating properly, /// Using this node might result in the document not updating properly,
@@ -98,6 +103,10 @@ impl<T, I, CachedNode> ImpureMemoNode<I, T, CachedNode> {
} }
} }
pub mod impure_memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
}
/// Stores both what a node was called with and what it returned. /// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct IORecord<I, O> { pub struct IORecord<I, O> {
@@ -142,7 +151,10 @@ impl<I, T, N> MonitorNode<I, T, N> {
} }
} }
use std::hash::{Hash, Hasher}; pub mod monitor {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct MemoHash<T: Hash> { pub struct MemoHash<T: Hash> {
hash: u64, hash: u64,
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend}; use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
@@ -103,11 +103,11 @@ pub enum RegistryValueSource {
Scope(&'static str), Scope(&'static str),
} }
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>; type NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<(NodeConstructor, NodeIOTypes)>>>>;
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>; pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
-1
View File
@@ -20,7 +20,6 @@ async fn transform<T: 'n + 'static>(
rotate: f64, rotate: f64,
scale: DVec2, scale: DVec2,
skew: DVec2, skew: DVec2,
_pivot: DVec2,
) -> Instances<T> { ) -> Instances<T> {
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
+33 -16
View File
@@ -1,6 +1,7 @@
use std::any::TypeId; use std::any::TypeId;
pub use std::borrow::Cow; pub use std::borrow::Cow;
use std::ops::Deref;
#[macro_export] #[macro_export]
macro_rules! concrete { macro_rules! concrete {
@@ -128,12 +129,37 @@ impl std::fmt::Debug for NodeIOTypes {
pub struct ProtoNodeIdentifier { pub struct ProtoNodeIdentifier {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
} }
impl From<String> for ProtoNodeIdentifier { impl From<String> for ProtoNodeIdentifier {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self { name: Cow::Owned(value) } Self { name: Cow::Owned(value) }
} }
} }
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}
impl Deref for ProtoNodeIdentifier {
type Target = str;
fn deref(&self) -> &Self::Target {
self.name.as_ref()
}
}
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> { fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
@@ -306,6 +332,13 @@ impl Type {
Self::Future(output) => output.replace_nested(f), Self::Future(output) => output.replace_nested(f),
} }
} }
pub fn to_cow_string(&self) -> Cow<'static, str> {
match self {
Type::Generic(name) => name.clone(),
_ => Cow::Owned(self.to_string()),
}
}
} }
fn format_type(ty: &str) -> String { fn format_type(ty: &str) -> String {
@@ -343,19 +376,3 @@ impl std::fmt::Display for Type {
write!(f, "{}", result) write!(f, "{}", result)
} }
} }
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}
@@ -1,7 +1,7 @@
use super::*; use super::*;
use crate::Ctx; use crate::Ctx;
use crate::instances::Instance; use crate::instances::Instance;
use crate::uuid::generate_uuid; use crate::uuid::{NodeId, generate_uuid};
use bezier_rs::BezierHandles; use bezier_rs::BezierHandles;
use dyn_any::DynAny; use dyn_any::DynAny;
use kurbo::{BezPath, PathEl, Point}; use kurbo::{BezPath, PathEl, Point};
@@ -420,12 +420,17 @@ impl Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`]. /// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable { async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
if vector_data.is_empty() { if vector_data.is_empty() {
vector_data.push(Instance::default()); vector_data.push(Instance::default());
} }
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item"); let vector_data_instance = vector_data.get_mut(0).expect("push should give one item");
modification.apply(vector_data_instance.instance); modification.apply(vector_data_instance.instance);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path);
if vector_data.len() > 1 { if vector_data.len() > 1 {
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len()); warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len());
} }
+1 -2
View File
@@ -352,8 +352,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) {
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); instance.transform = transform * instance.transform;
instance.transform = transform * local_matrix;
result_table.push(instance); result_table.push(instance);
} }
+20
View File
@@ -612,6 +612,20 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
vector_a.dot(vector_b) vector_a.dot(vector_b)
} }
/// Gets the length or magnitude of a vector.
#[node_macro::node(category("Math: Vector"))]
fn length(_: impl Ctx, vector: DVec2) -> f64 {
vector.length()
}
/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude.
///
/// Returns zero when the input vector is zero.
#[node_macro::node(category("Math: Vector"))]
fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 {
vector.normalize_or_zero()
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@@ -625,6 +639,12 @@ mod test {
assert_eq!(dot_product((), vector_a, vector_b), 11.); assert_eq!(dot_product((), vector_a, vector_b), 11.);
} }
#[test]
pub fn length_function() {
let vector = DVec2::new(3., 4.);
assert_eq!(length((), vector), 5.);
}
#[test] #[test]
fn test_basic_expression() { fn test_basic_expression() {
let result = math((), 0., "2 + 2".to_string(), 0.); let result = math((), 0., "2 + 2".to_string(), 0.);
+3 -8
View File
@@ -486,10 +486,6 @@ impl DocumentNodeImplementation {
} }
} }
pub const fn proto(name: &'static str) -> Self {
Self::ProtoNode(ProtoNodeIdentifier::new(name))
}
pub fn output_count(&self) -> usize { pub fn output_count(&self) -> usize {
match self { match self {
DocumentNodeImplementation::Network(network) => network.exports.len(), DocumentNodeImplementation::Network(network) => network.exports.len(),
@@ -1268,7 +1264,6 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
mod test { mod test {
use super::*; use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::ProtoNodeIdentifier;
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
fn gen_node_id() -> NodeId { fn gen_node_id() -> NodeId {
@@ -1540,7 +1535,7 @@ mod test {
NodeId(1), NodeId(1),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)], inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
@@ -1548,7 +1543,7 @@ mod test {
NodeId(2), NodeId(2),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 1)], inputs: vec![NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
@@ -1575,7 +1570,7 @@ mod test {
NodeId(2), NodeId(2),
DocumentNode { DocumentNode {
inputs: vec![result_node_input], inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
+3 -3
View File
@@ -97,7 +97,7 @@ macro_rules! tagged_value {
} }
} }
/// Attempts to downcast the dynamic type to a tagged value /// Attempts to downcast the dynamic type to a tagged value
pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result<Self, String> { pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
use std::any::TypeId; use std::any::TypeId;
match input.type_id() { match input.type_id() {
@@ -190,9 +190,9 @@ tagged_value! {
VectorData(graphene_core::vector::VectorDataTable), VectorData(graphene_core::vector::VectorDataTable),
#[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
RasterData(graphene_core::raster_types::RasterDataTable<CPU>), RasterData(graphene_core::raster_types::RasterDataTable<CPU>),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable), GraphicGroup(graphene_core::GraphicGroupTable),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(graphene_core::ArtboardGroupTable), ArtboardGroup(graphene_core::ArtboardGroupTable),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
+1 -7
View File
@@ -18,7 +18,6 @@ use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegme
use base64::Engine; use base64::Engine;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use glam::DAffine2; use glam::DAffine2;
use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@@ -278,12 +277,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
#[cfg(all(feature = "vello", not(test)))] #[cfg(all(feature = "vello", not(test)))]
let use_vello = use_vello && surface_handle.is_some(); let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata { let mut metadata = RenderMetadata::default();
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
};
data.collect_metadata(&mut metadata, footprint, None); data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format; let output_format = render_config.export_format;
+2
View File
@@ -198,6 +198,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
pub struct RenderMetadata { pub struct RenderMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>, pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
} }
@@ -1090,6 +1091,7 @@ impl GraphicElementRendered for GraphicElement {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table // TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() { if let Some(vector_data) = vector_data.instance_ref_iter().next() {
metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform); metadata.local_transforms.insert(element_id, *vector_data.transform);
} }
} }
+1 -1
View File
@@ -20,7 +20,7 @@ mod tests {
NodeId(0), NodeId(0),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)], inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
+2 -2
View File
@@ -39,7 +39,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
// TODO: Add conversion step // TODO: Add conversion step
@@ -68,7 +68,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
inner_network, inner_network,
render_node, render_node,
DocumentNode { DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default() ..Default::default()
}, },
+57 -52
View File
@@ -2,7 +2,7 @@ use crate::parsing::*;
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro_crate::FoundCrate; use proc_macro_crate::FoundCrate;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned}; use quote::{ToTokens, format_ident, quote, quote_spanned};
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
@@ -330,11 +330,15 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
}) })
} }
}; };
let path = match parsed.attributes.path {
Some(ref path) => quote!(stringify!(#path).replace(' ', "")), let identifier = format_ident!("{}_proto_ident", fn_name);
None => quote!(std::module_path!().rsplit_once("::").unwrap().0), let identifier_path = match parsed.attributes.path.as_ref() {
Some(path) => {
let path = path.to_token_stream().to_string().replace(' ', "");
quote!(#path)
}
None => quote!(std::module_path!()),
}; };
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?; let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake)); let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
@@ -354,6 +358,11 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
{ {
#eval_impl #eval_impl
} }
const fn #identifier() -> #graphene_core::ProtoNodeIdentifier {
#graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name)))
}
#[doc(inline)] #[doc(inline)]
pub use #mod_name::#struct_name; pub use #mod_name::#struct_name;
@@ -418,67 +427,63 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
)* )*
], ],
}; };
NODE_METADATA.lock().unwrap().insert(#identifier, metadata); NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
} }
} }
}) })
} }
/// Generates strongly typed utilites to access inputs /// Generates strongly typed utilites to access inputs
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &TokenStream2) -> TokenStream2 { fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 {
if parsed.attributes.skip_impl {
return quote! {};
}
let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake));
let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);
let mut generated_input_accessor = Vec::new(); let mut generated_input_accessor = Vec::new();
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { if !parsed.attributes.skip_impl {
let mut ty = match parsed_input { let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);
ParsedField::Regular { ty, .. } => ty,
ParsedField::Node { output_type, .. } => output_type, for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
let mut ty = match parsed_input {
ParsedField::Regular { ty, .. } => ty,
ParsedField::Node { output_type, .. } => output_type,
}
.clone();
// We only want the necessary generics.
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
// TODO: figure out a better name that doesn't conflict with so many types
let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal));
let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter());
// Only create structs with phantom data where necessary.
generated_input_accessor.push(if phantom_data_declerations.is_empty() {
quote! {
pub struct #struct_name;
}
} else {
quote! {
pub struct #struct_name <#(#used),*>{
#(#phantom_data_declerations,)*
}
}
});
generated_input_accessor.push(quote! {
impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index;
fn identifier() -> #graphene_core::ProtoNodeIdentifier {
#inputs_module_name::IDENTIFIER.clone()
}
type Result = #ty;
}
})
} }
.clone();
// We only want the necessary generics.
let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty);
// TODO: figure out a better name that doesn't conflict with so many types
let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal));
let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter());
// Only create structs with phantom data where necessary.
generated_input_accessor.push(if phantom_data_declerations.is_empty() {
quote! {
pub struct #struct_name;
}
} else {
quote! {
pub struct #struct_name <#(#used),*>{
#(#phantom_data_declerations,)*
}
}
});
generated_input_accessor.push(quote! {
impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index;
fn identifier() -> &'static str {
protonode_identifier()
}
type Result = #ty;
}
})
} }
quote! { quote! {
pub mod #inputs_module_name { pub mod #inputs_module_name {
use super::*; use super::*;
pub fn protonode_identifier() -> &'static str { /// The `ProtoNodeIdentifier` of this node without any generics attached to it
// Storing the string in a once lock should reduce allocations (since we call this in a loop)? pub const IDENTIFIER: #graphene_core::ProtoNodeIdentifier = #identifier();
static NODE_NAME: std::sync::OnceLock<String> = std::sync::OnceLock::new();
NODE_NAME.get_or_init(|| #identifier )
}
#(#generated_input_accessor)* #(#generated_input_accessor)*
} }
} }
@@ -511,7 +516,7 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a crate::Generi
(fn_generic_params, phantom_data_declerations) (fn_generic_params, phantom_data_declerations)
} }
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &TokenStream2) -> Result<TokenStream2, Error> { fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result<TokenStream2, Error> {
if parsed.attributes.skip_impl { if parsed.attributes.skip_impl {
return Ok(quote!()); return Ok(quote!());
} }
@@ -604,7 +609,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
fn register_node() { fn register_node() {
let mut registry = NODE_REGISTRY.lock().unwrap(); let mut registry = NODE_REGISTRY.lock().unwrap();
registry.insert( registry.insert(
#identifier, #identifier(),
vec![ vec![
#(#constructors,)* #(#constructors,)*
] ]
+4 -4
View File
@@ -6,7 +6,7 @@ use graphene_std::registry::*;
use graphene_std::*; use graphene_std::*;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String, DocumentNode>) { pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>) {
if network.generated { if network.generated {
return; return;
} }
@@ -15,7 +15,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
match &mut node.implementation { match &mut node.implementation {
DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions), DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions),
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) { if let Some(new_node) = substitutions.get(proto_node_identifier) {
node.implementation = new_node.implementation.clone(); node.implementation = new_node.implementation.clone();
} }
} }
@@ -24,7 +24,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
} }
} }
pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> { pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {
let mut custom = HashMap::new(); let mut custom = HashMap::new();
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() {
@@ -49,7 +49,7 @@ pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> {
let input_count = inputs.len(); let input_count = inputs.len();
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); let identity_node = ops::identity::IDENTIFIER;
let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY;