mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 02:48:11 +08:00
Merge branch 'master' into refit_on_delete
This commit is contained in:
@@ -55,53 +55,3 @@ pub fn commit_info_localized(localized_commit_date: &str) -> String {
|
||||
localized_commit_date
|
||||
)
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod test {
|
||||
// use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||
// use crate::messages::prelude::*;
|
||||
|
||||
// // TODO: Fix and reenable
|
||||
// #[ignore]
|
||||
// #[test]
|
||||
// fn debug_ub() {
|
||||
// use super::Message;
|
||||
|
||||
// let mut editor = super::Editor::new();
|
||||
// let mut responses = Vec::new();
|
||||
|
||||
// let messages: Vec<Message> = vec![
|
||||
// Message::Init,
|
||||
// Message::Preferences(PreferencesMessage::Load {
|
||||
// preferences: r#"{ "imaginate_server_hostname": "http://localhost:7860/", "imaginate_refresh_frequency": 1, "zoom_with_scroll": false }"#.to_string(),
|
||||
// }),
|
||||
// PortfolioMessage::OpenDocumentFileWithId {
|
||||
// document_id: DocumentId(0),
|
||||
// document_name: "".into(),
|
||||
// document_is_auto_saved: true,
|
||||
// document_is_saved: true,
|
||||
// document_serialized_content: r#" [removed until test is reenabled] "#.into(),
|
||||
// to_front: false,
|
||||
// }
|
||||
// .into(),
|
||||
// InputPreprocessorMessage::BoundsOfViewports {
|
||||
// bounds_of_viewports: vec![ViewportBounds::from_slice(&[0., 0., 1920., 1080.])],
|
||||
// }
|
||||
// .into(),
|
||||
// ];
|
||||
|
||||
// use futures::executor::block_on;
|
||||
// for message in messages {
|
||||
// block_on(crate::node_graph_executor::run_node_graph());
|
||||
// let mut res = VecDeque::new();
|
||||
// editor.poll_node_graph_evaluation(&mut res).expect("poll_node_graph_evaluation failed");
|
||||
|
||||
// let res = editor.handle_message(message);
|
||||
// responses.push(res);
|
||||
// }
|
||||
// let responses = responses.pop().unwrap();
|
||||
// // let trigger_message = responses[responses.len() - 2].clone();
|
||||
|
||||
// println!("responses: {responses:#?}");
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -122,8 +122,8 @@ pub const DEFAULT_BRUSH_SIZE: f64 = 20.;
|
||||
// GIZMOS
|
||||
pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
|
||||
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
||||
pub const NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION: f64 = 1.2;
|
||||
pub const NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH: f64 = 10.;
|
||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||
|
||||
// SCROLLBARS
|
||||
|
||||
@@ -204,27 +204,6 @@ impl PreferencesDialogMessageHandler {
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
// TODO: Reenable when Imaginate is restored
|
||||
// let imaginate_server_hostname = vec![
|
||||
// TextLabel::new("Imaginate").min_width(60).italic(true).widget_holder(),
|
||||
// TextLabel::new("Server Hostname").table_align(true).widget_holder(),
|
||||
// TextInput::new(&preferences.imaginate_server_hostname)
|
||||
// .min_width(200)
|
||||
// .on_update(|text_input: &TextInput| PreferencesMessage::ImaginateServerHostname { hostname: text_input.value.clone() }.into())
|
||||
// .widget_holder(),
|
||||
// ];
|
||||
// let imaginate_refresh_frequency = vec![
|
||||
// TextLabel::new("").min_width(60).widget_holder(),
|
||||
// TextLabel::new("Refresh Frequency").table_align(true).widget_holder(),
|
||||
// NumberInput::new(Some(preferences.imaginate_refresh_frequency))
|
||||
// .unit(" seconds")
|
||||
// .min(0.)
|
||||
// .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
// .min_width(200)
|
||||
// .on_update(|number_input: &NumberInput| PreferencesMessage::ImaginateRefreshFrequency { seconds: number_input.value.unwrap() }.into())
|
||||
// .widget_holder(),
|
||||
// ];
|
||||
|
||||
Layout::WidgetLayout(WidgetLayout::new(vec![
|
||||
LayoutGroup::Row { widgets: navigation_header },
|
||||
LayoutGroup::Row { widgets: zoom_rate_label },
|
||||
@@ -238,8 +217,6 @@ impl PreferencesDialogMessageHandler {
|
||||
LayoutGroup::Row { widgets: graph_wire_style },
|
||||
LayoutGroup::Row { widgets: use_vello },
|
||||
LayoutGroup::Row { widgets: vector_meshes },
|
||||
// LayoutGroup::Row { widgets: imaginate_server_hostname },
|
||||
// LayoutGroup::Row { widgets: imaginate_refresh_frequency },
|
||||
]))
|
||||
}
|
||||
|
||||
|
||||
@@ -99,19 +99,6 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "copyText")]
|
||||
copy_text: String,
|
||||
},
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
TriggerUpgradeDocumentToVectorManipulationFormat {
|
||||
#[serde(rename = "documentId")]
|
||||
document_id: DocumentId,
|
||||
#[serde(rename = "documentName")]
|
||||
document_name: String,
|
||||
#[serde(rename = "documentIsAutoSaved")]
|
||||
document_is_auto_saved: bool,
|
||||
#[serde(rename = "documentIsSaved")]
|
||||
document_is_saved: bool,
|
||||
#[serde(rename = "documentSerializedContent")]
|
||||
document_serialized_content: String,
|
||||
},
|
||||
TriggerVisitLink {
|
||||
url: String,
|
||||
},
|
||||
|
||||
@@ -206,13 +206,6 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(ArrowUp); action_dispatch=ShapeToolMessage::IncreaseSides),
|
||||
entry!(KeyDown(ArrowDown); action_dispatch=ShapeToolMessage::DecreaseSides),
|
||||
//
|
||||
// ImaginateToolMessage
|
||||
// entry!(KeyDown(MouseLeft); action_dispatch=ImaginateToolMessage::DragStart),
|
||||
// entry!(KeyUp(MouseLeft); action_dispatch=ImaginateToolMessage::DragStop),
|
||||
// entry!(KeyDown(MouseRight); action_dispatch=ImaginateToolMessage::Abort),
|
||||
// entry!(KeyDown(Escape); action_dispatch=ImaginateToolMessage::Abort),
|
||||
// entry!(PointerMove; refresh_keys=[Alt, Shift], action_dispatch=ImaginateToolMessage::Resize { center: Alt, lock_ratio: Shift }),
|
||||
//
|
||||
// PathToolMessage
|
||||
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=PathToolMessage::DeleteAndBreakPath),
|
||||
|
||||
@@ -11,8 +11,8 @@ use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::renderer::ClickTarget;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::ClickTarget;
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
|
||||
#[impl_message(Message, PortfolioMessage, Document)]
|
||||
@@ -73,13 +73,6 @@ pub enum DocumentMessage {
|
||||
GroupSelectedLayers {
|
||||
group_folder_type: GroupFolderType,
|
||||
},
|
||||
// ImaginateGenerate {
|
||||
// imaginate_node: Vec<NodeId>,
|
||||
// },
|
||||
// ImaginateRandom {
|
||||
// imaginate_node: Vec<NodeId>,
|
||||
// then_generate: bool,
|
||||
// },
|
||||
MoveSelectedLayersTo {
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
|
||||
@@ -29,11 +29,13 @@ use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{Raster, RasterDataTable};
|
||||
use graphene_std::renderer::{ClickTarget, ClickTargetType, Quad};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use graphene_std::vector::{PointId, path_bool_lib};
|
||||
use std::time::Duration;
|
||||
|
||||
pub struct DocumentMessageData<'a> {
|
||||
@@ -613,37 +615,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });
|
||||
}
|
||||
}
|
||||
// DocumentMessage::ImaginateGenerate { imaginate_node } => {
|
||||
// let random_value = generate_uuid();
|
||||
// responses.add(NodeGraphMessage::SetInputValue {
|
||||
// node_id: *imaginate_node.last().unwrap(),
|
||||
// // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeDefinition` in `document_node_type.rs`
|
||||
// input_index: 17,
|
||||
// value: graph_craft::document::value::TaggedValue::U64(random_value),
|
||||
// });
|
||||
|
||||
// responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||
// }
|
||||
// DocumentMessage::ImaginateRandom { imaginate_node, then_generate } => {
|
||||
// // Generate a random seed. We only want values between -2^53 and 2^53, because integer values
|
||||
// // outside of this range can get rounded in f64
|
||||
// let random_bits = generate_uuid();
|
||||
// let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63)));
|
||||
|
||||
// responses.add(DocumentMessage::AddTransaction);
|
||||
// // Set a random seed input
|
||||
// responses.add(NodeGraphMessage::SetInputValue {
|
||||
// node_id: *imaginate_node.last().unwrap(),
|
||||
// // Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeDefinition` in `document_node_type.rs`
|
||||
// input_index: 3,
|
||||
// value: graph_craft::document::value::TaggedValue::F64(random_value),
|
||||
// });
|
||||
|
||||
// // Generate the image
|
||||
// if then_generate {
|
||||
// responses.add(DocumentMessage::ImaginateGenerate { imaginate_node });
|
||||
// }
|
||||
// }
|
||||
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
||||
if !self.selection_network_path.is_empty() {
|
||||
log::error!("Moving selected layers is only supported for the Document Network");
|
||||
@@ -1655,7 +1626,7 @@ impl DocumentMessageHandler {
|
||||
subpath.is_inside_subpath(&viewport_polygon, None, None)
|
||||
}
|
||||
ClickTargetType::FreePoint(point) => {
|
||||
let mut point = point.clone();
|
||||
let mut point = *point;
|
||||
point.apply_transform(layer_transform);
|
||||
viewport_polygon.contains_point(point.position)
|
||||
}
|
||||
@@ -2992,7 +2963,7 @@ impl<'a> ClickXRayIter<'a> {
|
||||
// We do this on this using the target area to reduce computation (as the target area is usually very simple).
|
||||
if clip && intersects {
|
||||
let clip_path = click_targets_to_path_lib_segments(click_targets.iter().flat_map(|x| x.iter()), transform);
|
||||
let subtracted = graphene_std::vector::boolean_intersect(path, clip_path).into_iter().flatten().collect::<Vec<_>>();
|
||||
let subtracted = boolean_intersect(path, clip_path).into_iter().flatten().collect::<Vec<_>>();
|
||||
if subtracted.is_empty() {
|
||||
use_children = false;
|
||||
} else {
|
||||
@@ -3377,9 +3348,9 @@ mod document_message_handler_tests {
|
||||
let rect_bbox_after = document.metadata().bounding_box_viewport(rect_layer).unwrap();
|
||||
|
||||
// Verifing the rectangle maintains approximately the same position in viewport space
|
||||
let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(100.0, 25.0)
|
||||
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0.0, -25.0), regression (#2688) causes it to be: DVec2(200.0, 75.0)
|
||||
let distance = before_center.distance(after_center); // TODO: Should be: 0.0, regression (#2688) causes it to be: 111.80339887498948
|
||||
let before_center = (rect_bbox_before[0] + rect_bbox_before[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(100., 25.)
|
||||
let after_center = (rect_bbox_after[0] + rect_bbox_after[1]) / 2.; // TODO: Should be: DVec2(0., -25.), regression (#2688) causes it to be: DVec2(200., 75.)
|
||||
let distance = before_center.distance(after_center); // TODO: Should be: 0., regression (#2688) causes it to be: 111.80339887498948
|
||||
|
||||
assert!(
|
||||
distance < 1.,
|
||||
|
||||
@@ -79,7 +79,7 @@ pub enum GraphOperationMessage {
|
||||
},
|
||||
NewBooleanOperationLayer {
|
||||
id: NodeId,
|
||||
operation: graphene_std::vector::misc::BooleanOperation,
|
||||
operation: graphene_std::path_bool::BooleanOperation,
|
||||
parent: LayerNodeIdentifier,
|
||||
insert_index: usize,
|
||||
},
|
||||
|
||||
+1
-1
@@ -10,8 +10,8 @@ use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::convert_usvg_path;
|
||||
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -134,7 +134,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
|
||||
}
|
||||
|
||||
pub fn insert_boolean_data(&mut self, operation: graphene_std::vector::misc::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
|
||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
mod document_node_derive;
|
||||
|
||||
use super::node_properties::choice::enum_choice;
|
||||
use super::node_properties::{self, ParameterWidgetsInfo};
|
||||
use super::utility_types::FrontendNodeType;
|
||||
@@ -91,7 +93,7 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeDefinition>> =
|
||||
/// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).
|
||||
/// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built.
|
||||
fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
let mut custom = vec![
|
||||
let custom = vec![
|
||||
// TODO: Auto-generate this from its proto node macro
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Identity",
|
||||
@@ -164,13 +166,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -241,21 +243,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"),
|
||||
manual_composition: Some(generic!(T)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// Primary (bottom) input type coercion
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"),
|
||||
manual_composition: Some(generic!(T)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
..Default::default()
|
||||
},
|
||||
// The monitor node is used to display a thumbnail in the UI
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
manual_composition: Some(generic!(T)),
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
skip_deduplication: true,
|
||||
..Default::default()
|
||||
},
|
||||
@@ -316,7 +318,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Layer".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(1, -3)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -333,7 +335,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("The Merge node combines graphical data through composition."),
|
||||
description: Cow::Borrowed("Merge attaches a layer to the stack's group."),
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
@@ -429,7 +431,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "To Artboard".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-10, -3)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -437,7 +439,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Monitor".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-2, -3)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -445,7 +447,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Append Artboards".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(6, -4)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
@@ -1464,7 +1466,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
// description: Cow::Borrowed("TODO"),
|
||||
// properties: None,
|
||||
// },
|
||||
// (*IMAGINATE_NODE).clone(),
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Path",
|
||||
category: "Vector",
|
||||
@@ -1649,7 +1650,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(DVec2), 5),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::TransformNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")),
|
||||
..Default::default()
|
||||
},
|
||||
]
|
||||
@@ -1733,7 +1734,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: vec![
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::vector::BooleanOperationNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1745,13 +1746,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1764,7 +1765,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||
NodeInput::value(TaggedValue::BooleanOperation(vector::misc::BooleanOperation::Union), false),
|
||||
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1822,12 +1823,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
properties: None,
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Sample Points",
|
||||
identifier: "Sample Polyline",
|
||||
category: "Vector: Modifier",
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)], // Taken from output 0 of Sample Points
|
||||
exports: vec![NodeInput::node(NodeId(4), 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
|
||||
@@ -1838,13 +1839,15 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(f64), 1), // From the document node's parameters
|
||||
NodeInput::network(concrete!(f64), 2), // From the document node's parameters
|
||||
NodeInput::network(concrete!(f64), 3), // From the document node's parameters
|
||||
NodeInput::network(concrete!(bool), 4), // From the document node's parameters
|
||||
NodeInput::node(NodeId(0), 0), // From output 0 of SubpathSegmentLengthsNode
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(f64), 3),
|
||||
NodeInput::network(concrete!(f64), 4),
|
||||
NodeInput::network(concrete!(f64), 5),
|
||||
NodeInput::network(concrete!(bool), 6),
|
||||
NodeInput::node(NodeId(0), 0),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePointsNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1856,13 +1859,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1875,6 +1878,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
@@ -1889,14 +1894,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Subpath Segment Lengths".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 5)),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 7)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNodeMetadata {
|
||||
persistent_metadata: DocumentNodePersistentMetadata {
|
||||
display_name: "Sample Points".to_string(),
|
||||
display_name: "Sample Polyline".to_string(),
|
||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1937,18 +1942,28 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
}),
|
||||
input_properties: vec![
|
||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
|
||||
PropertiesRow::with_override(
|
||||
"Spacing",
|
||||
"Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).",
|
||||
"Separation",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(1.),
|
||||
min: Some(0.),
|
||||
unit: Some(" px".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
"Quantity",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(2.),
|
||||
is_integer: true,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
"Start Offset",
|
||||
"Exclude some distance from the start of the path before the first instance.",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(0.),
|
||||
unit: Some(" px".to_string()),
|
||||
@@ -1957,21 +1972,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
"Stop Offset",
|
||||
"Exclude some distance from the end of the path after the last instance.",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(0.),
|
||||
unit: Some(" px".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
Into::<PropertiesRow>::into(("Adaptive Spacing", "Round 'Spacing' to a nearby value that divides into the path length evenly.")),
|
||||
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("Convert vector geometry into a polyline composed of evenly spaced points."),
|
||||
properties: None,
|
||||
properties: Some("sample_polyline_properties"),
|
||||
},
|
||||
DocumentNodeDefinition {
|
||||
identifier: "Scatter Points",
|
||||
@@ -1999,13 +2014,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::FreezeRealTimeNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform::BoundlessFootprintNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -2100,233 +2115,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
},
|
||||
];
|
||||
|
||||
// Remove struct generics
|
||||
for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() {
|
||||
let NodeTemplate {
|
||||
document_node: DocumentNode { implementation, .. },
|
||||
..
|
||||
} = node_template;
|
||||
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation {
|
||||
if let Some((new_name, _suffix)) = name.rsplit_once("<") {
|
||||
*name = Cow::Owned(new_name.to_string())
|
||||
}
|
||||
};
|
||||
}
|
||||
let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap();
|
||||
'outer: for (id, metadata) in graphene_std::registry::NODE_METADATA.lock().unwrap().iter() {
|
||||
use graphene_std::registry::*;
|
||||
let id = id.clone();
|
||||
|
||||
for node in custom.iter() {
|
||||
let DocumentNodeDefinition {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode { implementation, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
} = node;
|
||||
match implementation {
|
||||
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let NodeMetadata {
|
||||
display_name,
|
||||
category,
|
||||
fields,
|
||||
description,
|
||||
properties,
|
||||
} = metadata;
|
||||
let Some(implementations) = &node_registry.get(&id) else { continue };
|
||||
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
|
||||
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
|
||||
let mut input_type = &first_node_io.call_argument;
|
||||
if valid_inputs.len() > 1 {
|
||||
input_type = &const { generic!(D) };
|
||||
}
|
||||
let output_type = &first_node_io.return_value;
|
||||
|
||||
let inputs = fields
|
||||
.iter()
|
||||
.zip(first_node_io.inputs.iter())
|
||||
.enumerate()
|
||||
.map(|(index, (field, node_io_ty))| {
|
||||
let ty = field.default_type.as_ref().unwrap_or(node_io_ty);
|
||||
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
|
||||
|
||||
match field.value_source {
|
||||
RegistryValueSource::None => {}
|
||||
RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed),
|
||||
RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)),
|
||||
};
|
||||
|
||||
if let Some(type_default) = TaggedValue::from_type(ty) {
|
||||
return NodeInput::value(type_default, exposed);
|
||||
}
|
||||
NodeInput::value(TaggedValue::None, true)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let node = DocumentNodeDefinition {
|
||||
identifier: display_name,
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
||||
visible: true,
|
||||
skip_deduplication: false,
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// TODO: Store information for input overrides in the node macro
|
||||
input_properties: fields
|
||||
.iter()
|
||||
.map(|f| match f.widget_override {
|
||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
has_primary_output: true,
|
||||
locked: false,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
category: category.unwrap_or("UNCATEGORIZED"),
|
||||
description: Cow::Borrowed(description),
|
||||
properties: *properties,
|
||||
};
|
||||
custom.push(node);
|
||||
}
|
||||
custom
|
||||
document_node_derive::post_process_nodes(custom)
|
||||
}
|
||||
|
||||
// pub static IMAGINATE_NODE: Lazy<DocumentNodeDefinition> = Lazy::new(|| DocumentNodeDefinition {
|
||||
// identifier: "Imaginate",
|
||||
// category: "Raster",
|
||||
// node_template: NodeTemplate {
|
||||
// document_node: DocumentNode {
|
||||
// implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||
// exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||
// nodes: [
|
||||
// DocumentNode {
|
||||
// inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0)],
|
||||
// implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"),
|
||||
// manual_composition: Some(concrete!(Context)),
|
||||
// skip_deduplication: true,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// DocumentNode {
|
||||
// inputs: vec![
|
||||
// NodeInput::node(NodeId(0), 0),
|
||||
// NodeInput::network(concrete!(&WasmEditorApi), 1),
|
||||
// NodeInput::network(concrete!(ImaginateController), 2),
|
||||
// NodeInput::network(concrete!(f64), 3),
|
||||
// NodeInput::network(concrete!(Option<DVec2>), 4),
|
||||
// NodeInput::network(concrete!(u32), 5),
|
||||
// NodeInput::network(concrete!(ImaginateSamplingMethod), 6),
|
||||
// NodeInput::network(concrete!(f64), 7),
|
||||
// NodeInput::network(concrete!(String), 8),
|
||||
// NodeInput::network(concrete!(String), 9),
|
||||
// NodeInput::network(concrete!(bool), 10),
|
||||
// NodeInput::network(concrete!(f64), 11),
|
||||
// NodeInput::network(concrete!(bool), 12),
|
||||
// NodeInput::network(concrete!(f64), 13),
|
||||
// NodeInput::network(concrete!(ImaginateMaskStartingFill), 14),
|
||||
// NodeInput::network(concrete!(bool), 15),
|
||||
// NodeInput::network(concrete!(bool), 16),
|
||||
// NodeInput::network(concrete!(u64), 17),
|
||||
// ],
|
||||
// implementation: DocumentNodeImplementation::proto("graphene_std::raster::ImaginateNode"),
|
||||
// ..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),
|
||||
// NodeInput::scope("editor-api"),
|
||||
// NodeInput::value(TaggedValue::ImaginateController(Default::default()), false),
|
||||
// NodeInput::value(TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
|
||||
// NodeInput::value(TaggedValue::OptionalDVec2(None), false),
|
||||
// NodeInput::value(TaggedValue::U32(30), false),
|
||||
// NodeInput::value(TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
|
||||
// NodeInput::value(TaggedValue::F64(7.5), false),
|
||||
// NodeInput::value(TaggedValue::String(String::new()), false),
|
||||
// NodeInput::value(TaggedValue::String(String::new()), false),
|
||||
// NodeInput::value(TaggedValue::Bool(false), false),
|
||||
// NodeInput::value(TaggedValue::F64(66.), false),
|
||||
// NodeInput::value(TaggedValue::Bool(true), false),
|
||||
// NodeInput::value(TaggedValue::F64(4.), false),
|
||||
// NodeInput::value(TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false),
|
||||
// NodeInput::value(TaggedValue::Bool(false), false),
|
||||
// NodeInput::value(TaggedValue::Bool(false), false),
|
||||
// NodeInput::value(TaggedValue::U64(0), false),
|
||||
// ],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// network_metadata: Some(NodeNetworkMetadata {
|
||||
// persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
// node_metadata: [
|
||||
// DocumentNodeMetadata {
|
||||
// persistent_metadata: DocumentNodePersistentMetadata {
|
||||
// display_name: "Monitor".to_string(),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// },
|
||||
// DocumentNodeMetadata {
|
||||
// persistent_metadata: DocumentNodePersistentMetadata {
|
||||
// display_name: "Imaginate".to_string(),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ]
|
||||
// .into_iter()
|
||||
// .enumerate()
|
||||
// .map(|(id, node)| (NodeId(id as u64), node))
|
||||
// .collect(),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// input_properties: vec![
|
||||
// "Input Image".into(),
|
||||
// "Editor Api".into(),
|
||||
// "Controller".into(),
|
||||
// "Seed".into(),
|
||||
// "Resolution".into(),
|
||||
// "Samples".into(),
|
||||
// "Sampling Method".into(),
|
||||
// "Prompt Guidance".into(),
|
||||
// "Prompt".into(),
|
||||
// "Negative Prompt".into(),
|
||||
// "Adapt Input Image".into(),
|
||||
// "Image Creativity".into(),
|
||||
// "Inpaint".into(),
|
||||
// "Mask Blur".into(),
|
||||
// "Mask Starting Fill".into(),
|
||||
// "Improve Faces".into(),
|
||||
// "Tiling".into(),
|
||||
// ],
|
||||
// output_names: vec!["Image".to_string()],
|
||||
// ..Default::default()
|
||||
// },
|
||||
// },
|
||||
// description: Cow::Borrowed("TODO"),
|
||||
// properties: None, // Some(&node_properties::imaginate_properties),
|
||||
// });
|
||||
|
||||
type NodeProperties = HashMap<String, Box<dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup> + Send + Sync>>;
|
||||
|
||||
pub static NODE_OVERRIDES: once_cell::sync::Lazy<NodeProperties> = once_cell::sync::Lazy::new(static_node_properties);
|
||||
@@ -2344,6 +2135,7 @@ fn static_node_properties() -> NodeProperties {
|
||||
map.insert("math_properties".to_string(), Box::new(node_properties::math_properties));
|
||||
map.insert("rectangle_properties".to_string(), Box::new(node_properties::rectangle_properties));
|
||||
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
|
||||
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||
map.insert(
|
||||
"identity_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),
|
||||
@@ -3060,19 +2852,3 @@ impl DocumentNodeDefinition {
|
||||
self.node_template_input_override(self.node_template.document_node.inputs.clone().into_iter().map(Some))
|
||||
}
|
||||
}
|
||||
|
||||
// Previously used by the Imaginate node, but usage was commented out since it did nothing.
|
||||
// pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
|
||||
// let mut network = NodeNetwork { ..Default::default() };
|
||||
// network.push_node_to_document_network(
|
||||
// resolve_document_node_type("Input Frame")
|
||||
// .expect("Input Frame node does not exist")
|
||||
// .to_document_node_default_inputs([], DocumentNodeMetadata::position((8, 4))),
|
||||
// );
|
||||
// network.push_node_to_document_network(
|
||||
// resolve_document_node_type("Output")
|
||||
// .expect("Output node does not exist")
|
||||
// .to_document_node([NodeInput::node(output_node_id, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
|
||||
// );
|
||||
// network
|
||||
// }
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
use super::DocumentNodeDefinition;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::registry::*;
|
||||
use graphene_std::*;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec<DocumentNodeDefinition> {
|
||||
// Remove struct generics
|
||||
for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() {
|
||||
let NodeTemplate {
|
||||
document_node: DocumentNode { implementation, .. },
|
||||
..
|
||||
} = node_template;
|
||||
|
||||
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation {
|
||||
if let Some((new_name, _suffix)) = name.rsplit_once("<") {
|
||||
*name = Cow::Owned(new_name.to_string())
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
|
||||
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
|
||||
for node in custom.iter() {
|
||||
let DocumentNodeDefinition {
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode { implementation, .. },
|
||||
..
|
||||
},
|
||||
..
|
||||
} = node;
|
||||
match implementation {
|
||||
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let NodeMetadata {
|
||||
display_name,
|
||||
category,
|
||||
fields,
|
||||
description,
|
||||
properties,
|
||||
} = metadata;
|
||||
|
||||
let Some(implementations) = &node_registry.get(id) else { continue };
|
||||
|
||||
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
|
||||
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
|
||||
|
||||
let input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument };
|
||||
let output_type = &first_node_io.return_value;
|
||||
|
||||
let inputs = preprocessor::node_inputs(fields, first_node_io);
|
||||
let node = DocumentNodeDefinition {
|
||||
identifier: display_name,
|
||||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
inputs,
|
||||
manual_composition: Some(input_type.clone()),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
||||
visible: true,
|
||||
skip_deduplication: false,
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// TODO: Store information for input overrides in the node macro
|
||||
input_properties: fields
|
||||
.iter()
|
||||
.map(|f| match f.widget_override {
|
||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
has_primary_output: true,
|
||||
locked: false,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
category: category.unwrap_or("UNCATEGORIZED"),
|
||||
description: Cow::Borrowed(description),
|
||||
properties: *properties,
|
||||
};
|
||||
|
||||
custom.push(node);
|
||||
}
|
||||
|
||||
custom
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::*;
|
||||
use renderer::Quad;
|
||||
use std::cmp::Ordering;
|
||||
@@ -1396,12 +1397,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
input,
|
||||
});
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
if (network_interface
|
||||
.reference(&node_id, selection_network_path)
|
||||
.is_none_or(|reference| *reference != Some("Imaginate".to_string())) // TODO: Potentially remove the reference to Imaginate
|
||||
|| input_index == 0)
|
||||
&& network_interface.connected_to_output(&node_id, selection_network_path)
|
||||
{
|
||||
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graphene_std::animation::RealTimeMode;
|
||||
use graphene_std::ops::XY;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::raster::curve::Curve;
|
||||
use graphene_std::raster::{
|
||||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||
@@ -22,9 +23,9 @@ use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::transform::{Footprint, ReferencePoint};
|
||||
use graphene_std::vector::VectorDataTable;
|
||||
use graphene_std::vector::misc::CentroidType;
|
||||
use graphene_std::vector::misc::GridType;
|
||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||
use graphene_std::vector::misc::{BooleanOperation, GridType};
|
||||
use graphene_std::vector::misc::{CentroidType, PointSpacingType};
|
||||
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops};
|
||||
use graphene_std::vector::style::{GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
||||
use graphene_std::{GraphicGroupTable, NodeInputDecleration};
|
||||
@@ -238,6 +239,7 @@ pub(crate) fn property_from_type(
|
||||
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
|
||||
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
|
||||
@@ -1225,6 +1227,64 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
widgets
|
||||
}
|
||||
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SPACING: &str = "Use a point sampling density controlled by a distance between, or specific number of, points.";
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_SEPARATION: &str = "Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled).";
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_QUANTITY: &str = "Number of points to place along the path.";
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_START_OFFSET: &str = "Exclude some distance from the start of the path before the first instance.";
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET: &str = "Exclude some distance from the end of the path after the last instance.";
|
||||
pub(crate) const SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING: &str = "Round 'Separation' to a nearby value that divides into the path length evenly.";
|
||||
|
||||
pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::sample_polyline::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in sample_polyline_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let current_spacing = document_node.inputs.get(SpacingInput::INDEX).and_then(|input| input.as_value()).cloned();
|
||||
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||
|
||||
let spacing = enum_choice::<PointSpacingType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let separation = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let quantity = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
||||
NumberInput::default().min(2.).int(),
|
||||
);
|
||||
let start_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let stop_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let adaptive_spacing = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
CheckboxInput::default().disabled(is_quantity),
|
||||
);
|
||||
|
||||
vec![
|
||||
spacing.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SPACING),
|
||||
match current_spacing {
|
||||
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_SEPARATION),
|
||||
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_QUANTITY),
|
||||
_ => LayoutGroup::Row { widgets: vec![] },
|
||||
},
|
||||
LayoutGroup::Row { widgets: start_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_START_OFFSET),
|
||||
LayoutGroup::Row { widgets: stop_offset }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET),
|
||||
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip(SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING),
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::exposure::*;
|
||||
|
||||
|
||||
@@ -1,547 +0,0 @@
|
||||
//! This has all been copied out of node_properties.rs to avoid leaving hundreds of lines of commented out code in that file. It's left here instead for future reference.
|
||||
|
||||
// pub fn imaginate_sampling_method(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
// let ParameterWidgetsInfo { node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
// vec![
|
||||
// DropdownInput::new(
|
||||
// ImaginateSamplingMethod::list()
|
||||
// .into_iter()
|
||||
// .map(|method| {
|
||||
// vec![
|
||||
// MenuListEntry::new(format!("{:?}", method))
|
||||
// .label(method.to_string())
|
||||
// .on_update(update_value(move |_| TaggedValue::ImaginateSamplingMethod(method), node_id, index)),
|
||||
// ]
|
||||
// })
|
||||
// .collect(),
|
||||
// )
|
||||
// .widget_holder(),
|
||||
// ]
|
||||
// .into()
|
||||
// }
|
||||
|
||||
// pub fn imaginate_mask_starting_fill(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
// let ParameterWidgetsInfo { node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
// vec![
|
||||
// DropdownInput::new(
|
||||
// ImaginateMaskStartingFill::list()
|
||||
// .into_iter()
|
||||
// .map(|fill| {
|
||||
// vec![
|
||||
// MenuListEntry::new(format!("{:?}", fill))
|
||||
// .label(fill.to_string())
|
||||
// .on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(fill), node_id, index)),
|
||||
// ]
|
||||
// })
|
||||
// .collect(),
|
||||
// )
|
||||
// .widget_holder(),
|
||||
// ]
|
||||
// .into()
|
||||
// }
|
||||
|
||||
// pub(crate) fn imaginate_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
// let imaginate_node = [context.selection_network_path, &[node_id]].concat();
|
||||
|
||||
// let resolve_input = |name: &str| {
|
||||
// IMAGINATE_NODE
|
||||
// .default_node_template()
|
||||
// .persistent_node_metadata
|
||||
// .input_properties
|
||||
// .iter()
|
||||
// .position(|row| row.input_name.as_str() == name)
|
||||
// .unwrap_or_else(|| panic!("Input {name} not found"))
|
||||
// };
|
||||
// let seed_index = resolve_input("Seed");
|
||||
// let resolution_index = resolve_input("Resolution");
|
||||
// let samples_index = resolve_input("Samples");
|
||||
// let sampling_method_index = resolve_input("Sampling Method");
|
||||
// let text_guidance_index = resolve_input("Prompt Guidance");
|
||||
// let text_index = resolve_input("Prompt");
|
||||
// let neg_index = resolve_input("Negative Prompt");
|
||||
// let base_img_index = resolve_input("Adapt Input Image");
|
||||
// let img_creativity_index = resolve_input("Image Creativity");
|
||||
// // let mask_index = resolve_input("Masking Layer");
|
||||
// // let inpaint_index = resolve_input("Inpaint");
|
||||
// // let mask_blur_index = resolve_input("Mask Blur");
|
||||
// // let mask_fill_index = resolve_input("Mask Starting Fill");
|
||||
// let faces_index = resolve_input("Improve Faces");
|
||||
// let tiling_index = resolve_input("Tiling");
|
||||
|
||||
// let document_node = match get_document_node(node_id, context) {
|
||||
// Ok(document_node) => document_node,
|
||||
// Err(err) => {
|
||||
// log::error!("Could not get document node in imaginate_properties: {err}");
|
||||
// return Vec::new();
|
||||
// }
|
||||
// };
|
||||
// let controller = &document_node.inputs[resolve_input("Controller")];
|
||||
|
||||
// let server_status = {
|
||||
// let server_status = context.persistent_data.imaginate.server_status();
|
||||
// let status_text = server_status.to_text();
|
||||
// let mut widgets = vec![
|
||||
// TextLabel::new("Server").widget_holder(),
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// IconButton::new("Settings", 24)
|
||||
// .tooltip("Preferences: Imaginate")
|
||||
// .on_update(|_| DialogMessage::RequestPreferencesDialog.into())
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// TextLabel::new(status_text).bold(true).widget_holder(),
|
||||
// Separator::new(SeparatorType::Related).widget_holder(),
|
||||
// IconButton::new("Reload", 24)
|
||||
// .tooltip("Refresh connection status")
|
||||
// .on_update(|_| PortfolioMessage::ImaginateCheckServerStatus.into())
|
||||
// .widget_holder(),
|
||||
// ];
|
||||
// if let ImaginateServerStatus::Unavailable | ImaginateServerStatus::Failed(_) = server_status {
|
||||
// widgets.extend([
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// TextButton::new("Server Help")
|
||||
// .tooltip("Learn how to connect Imaginate to an image generation server")
|
||||
// .on_update(|_| {
|
||||
// FrontendMessage::TriggerVisitLink {
|
||||
// url: "https://github.com/GraphiteEditor/Graphite/discussions/1089".to_string(),
|
||||
// }
|
||||
// .into()
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// ]);
|
||||
// }
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Connection status to the server that computes generated images")
|
||||
// };
|
||||
|
||||
// let Some(TaggedValue::ImaginateController(controller)) = controller.as_value() else {
|
||||
// panic!("Invalid output status input")
|
||||
// };
|
||||
// let imaginate_status = controller.get_status();
|
||||
|
||||
// let use_base_image = if let Some(&TaggedValue::Bool(use_base_image)) = &document_node.inputs[base_img_index].as_value() {
|
||||
// use_base_image
|
||||
// } else {
|
||||
// true
|
||||
// };
|
||||
|
||||
// let transform_not_connected = false;
|
||||
|
||||
// let progress = {
|
||||
// let mut widgets = vec![TextLabel::new("Progress").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
// add_blank_assist(&mut widgets);
|
||||
// let status = imaginate_status.to_text();
|
||||
// widgets.push(TextLabel::new(status.as_ref()).bold(true).widget_holder());
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(match imaginate_status {
|
||||
// ImaginateStatus::Failed(_) => status.as_ref(),
|
||||
// _ => "When generating, the percentage represents how many sampling steps have so far been processed out of the target number",
|
||||
// })
|
||||
// };
|
||||
|
||||
// let image_controls = {
|
||||
// let mut widgets = vec![TextLabel::new("Image").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
|
||||
// match &imaginate_status {
|
||||
// ImaginateStatus::Beginning | ImaginateStatus::Uploading => {
|
||||
// add_blank_assist(&mut widgets);
|
||||
// widgets.push(TextButton::new("Beginning...").tooltip("Sending image generation request to the server").disabled(true).widget_holder());
|
||||
// }
|
||||
// ImaginateStatus::Generating(_) => {
|
||||
// add_blank_assist(&mut widgets);
|
||||
// widgets.push(
|
||||
// TextButton::new("Terminate")
|
||||
// .tooltip("Cancel the in-progress image generation and keep the latest progress")
|
||||
// .on_update({
|
||||
// let controller = controller.clone();
|
||||
// move |_| {
|
||||
// controller.request_termination();
|
||||
// Message::NoOp
|
||||
// }
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// );
|
||||
// }
|
||||
// ImaginateStatus::Terminating => {
|
||||
// add_blank_assist(&mut widgets);
|
||||
// widgets.push(
|
||||
// TextButton::new("Terminating...")
|
||||
// .tooltip("Waiting on the final image generated after termination")
|
||||
// .disabled(true)
|
||||
// .widget_holder(),
|
||||
// );
|
||||
// }
|
||||
// ImaginateStatus::Ready | ImaginateStatus::ReadyDone | ImaginateStatus::Terminated | ImaginateStatus::Failed(_) => widgets.extend_from_slice(&[
|
||||
// IconButton::new("Random", 24)
|
||||
// .tooltip("Generate with a new random seed")
|
||||
// .on_update({
|
||||
// let imaginate_node = imaginate_node.clone();
|
||||
// let controller = controller.clone();
|
||||
// move |_| {
|
||||
// controller.trigger_regenerate();
|
||||
// DocumentMessage::ImaginateRandom {
|
||||
// imaginate_node: imaginate_node.clone(),
|
||||
// then_generate: true,
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// TextButton::new("Generate")
|
||||
// .tooltip("Fill layer frame by generating a new image")
|
||||
// .on_update({
|
||||
// let controller = controller.clone();
|
||||
// let imaginate_node = imaginate_node.clone();
|
||||
// move |_| {
|
||||
// controller.trigger_regenerate();
|
||||
// DocumentMessage::ImaginateGenerate {
|
||||
// imaginate_node: imaginate_node.clone(),
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Related).widget_holder(),
|
||||
// TextButton::new("Clear")
|
||||
// .tooltip("Remove generated image from the layer frame")
|
||||
// .disabled(!matches!(imaginate_status, ImaginateStatus::ReadyDone))
|
||||
// .on_update({
|
||||
// let controller = controller.clone();
|
||||
// let imaginate_node = imaginate_node.clone();
|
||||
// move |_| {
|
||||
// controller.set_status(ImaginateStatus::Ready);
|
||||
// DocumentMessage::ImaginateGenerate {
|
||||
// imaginate_node: imaginate_node.clone(),
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// ]),
|
||||
// }
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Buttons that control the image generation process")
|
||||
// };
|
||||
|
||||
// // Requires custom layout for the regenerate button
|
||||
// let seed = {
|
||||
// let mut widgets = start_widgets(document_node, node_id, seed_index, "Seed", FrontendGraphDataType::Number, false);
|
||||
|
||||
// let Some(input) = document_node.inputs.get(seed_index) else {
|
||||
// log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
// return vec![];
|
||||
// };
|
||||
// if let Some(&TaggedValue::F64(seed)) = &input.as_non_exposed_value() {
|
||||
// widgets.extend_from_slice(&[
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// IconButton::new("Resync", 24)
|
||||
// .tooltip("Set a new random seed")
|
||||
// .on_update({
|
||||
// let imaginate_node = imaginate_node.clone();
|
||||
// move |_| {
|
||||
// DocumentMessage::ImaginateRandom {
|
||||
// imaginate_node: imaginate_node.clone(),
|
||||
// then_generate: false,
|
||||
// }
|
||||
// .into()
|
||||
// }
|
||||
// })
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// NumberInput::new(Some(seed))
|
||||
// .int()
|
||||
// .min(-((1_u64 << f64::MANTISSA_DIGITS) as f64))
|
||||
// .max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
// .on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap()), node_id, seed_index))
|
||||
// .on_commit(commit_value)
|
||||
// .mode(NumberInputMode::Increment)
|
||||
// .widget_holder(),
|
||||
// ])
|
||||
// }
|
||||
// // Note: Limited by f64. You cannot even have all the possible u64 values :)
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations")
|
||||
// };
|
||||
|
||||
// // let transform = context
|
||||
// // .executor
|
||||
// // .introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &RasterData<Color>| frame.transform)
|
||||
// // .unwrap_or_default();
|
||||
// let image_size = context
|
||||
// .executor
|
||||
// .introspect_node_in_network(
|
||||
// context.network_interface.document_network().unwrap(),
|
||||
// &imaginate_node,
|
||||
// |network| {
|
||||
// network
|
||||
// .nodes
|
||||
// .iter()
|
||||
// .find(|node| {
|
||||
// node.1
|
||||
// .inputs
|
||||
// .iter()
|
||||
// .any(|node_input| if let NodeInput::Network { import_index, .. } = node_input { *import_index == 0 } else { false })
|
||||
// })
|
||||
// .map(|(node_id, _)| node_id)
|
||||
// .copied()
|
||||
// },
|
||||
// |frame: &IORecord<(), RasterData<Color>>| (frame.output.image.width, frame.output.image.height),
|
||||
// )
|
||||
// .unwrap_or_default();
|
||||
|
||||
// let document_node = match get_document_node(node_id, context) {
|
||||
// Ok(document_node) => document_node,
|
||||
// Err(err) => {
|
||||
// log::error!("Could not get document node in imaginate_properties: {err}");
|
||||
// return Vec::new();
|
||||
// }
|
||||
// };
|
||||
|
||||
// let resolution = {
|
||||
// let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Number, false);
|
||||
|
||||
// let round = |size: DVec2| {
|
||||
// let (x, y) = graphene_std::imaginate::pick_safe_imaginate_resolution(size.into());
|
||||
// DVec2::new(x as f64, y as f64)
|
||||
// };
|
||||
|
||||
// let Some(input) = document_node.inputs.get(resolution_index) else {
|
||||
// log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
// return vec![];
|
||||
// };
|
||||
// if let Some(&TaggedValue::OptionalDVec2(vec2)) = &input.as_non_exposed_value() {
|
||||
// let dimensions_is_auto = vec2.is_none();
|
||||
// let vec2 = vec2.unwrap_or_else(|| round((image_size.0 as f64, image_size.1 as f64).into()));
|
||||
|
||||
// widgets.extend_from_slice(&[
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// IconButton::new("FrameAll", 24)
|
||||
// .tooltip("Set the layer dimensions to this resolution")
|
||||
// .on_update(move |_| DialogMessage::RequestComingSoonDialog { issue: None }.into())
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// CheckboxInput::new(!dimensions_is_auto || transform_not_connected)
|
||||
// .icon("Edit12px")
|
||||
// .tooltip({
|
||||
// let message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64)";
|
||||
// let manual_message = "Set a custom resolution instead of using the input's dimensions (rounded to the nearest 64).\n\
|
||||
// \n\
|
||||
// (Resolution must be set manually while the 'Transform' input is disconnected.)";
|
||||
|
||||
// if transform_not_connected {
|
||||
// manual_message
|
||||
// } else {
|
||||
// message
|
||||
// }
|
||||
// })
|
||||
// .disabled(transform_not_connected)
|
||||
// .on_update(update_value(
|
||||
// move |checkbox_input: &CheckboxInput| TaggedValue::OptionalDVec2(if checkbox_input.checked { Some(vec2) } else { None }),
|
||||
// node_id,
|
||||
// resolution_index,
|
||||
// ))
|
||||
// .on_commit(commit_value)
|
||||
// .for_label(checkbox_id.clone())
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Related).widget_holder(),
|
||||
// NumberInput::new(Some(vec2.x))
|
||||
// .label("W")
|
||||
// .min(64.)
|
||||
// .step(64.)
|
||||
// .unit(" px")
|
||||
// .disabled(dimensions_is_auto && !transform_not_connected)
|
||||
// .on_update(update_value(
|
||||
// move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(number_input.value.unwrap(), vec2.y)))),
|
||||
// node_id,
|
||||
// resolution_index,
|
||||
// ))
|
||||
// .on_commit(commit_value)
|
||||
// .widget_holder(),
|
||||
// Separator::new(SeparatorType::Related).widget_holder(),
|
||||
// NumberInput::new(Some(vec2.y))
|
||||
// .label("H")
|
||||
// .min(64.)
|
||||
// .step(64.)
|
||||
// .unit(" px")
|
||||
// .disabled(dimensions_is_auto && !transform_not_connected)
|
||||
// .on_update(update_value(
|
||||
// move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(vec2.x, number_input.value.unwrap())))),
|
||||
// node_id,
|
||||
// resolution_index,
|
||||
// ))
|
||||
// .on_commit(commit_value)
|
||||
// .widget_holder(),
|
||||
// ])
|
||||
// }
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// "Width and height of the image that will be generated. Larger resolutions take longer to compute.\n\
|
||||
// \n\
|
||||
// 512x512 yields optimal results because the AI is trained to understand that scale best. Larger sizes may tend to integrate the prompt's subject more than once. Small sizes are often incoherent.\n\
|
||||
// \n\
|
||||
// Dimensions must be a multiple of 64, so these are set by rounding the layer dimensions. A resolution exceeding 1 megapixel is reduced below that limit because larger sizes may exceed available GPU memory on the server.")
|
||||
// };
|
||||
|
||||
// let sampling_steps = {
|
||||
// let widgets = number_widget(document_node, node_id, samples_index, "Sampling Steps", NumberInput::default().min(0.).max(150.).int(), true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Number of iterations to improve the image generation quality, with diminishing returns around 40 when using the Euler A sampling method")
|
||||
// };
|
||||
|
||||
// let sampling_method = {
|
||||
// let mut widgets = start_widgets(document_node, node_id, sampling_method_index, "Sampling Method", FrontendGraphDataType::General, true);
|
||||
|
||||
// let Some(input) = document_node.inputs.get(sampling_method_index) else {
|
||||
// log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
// return vec![];
|
||||
// };
|
||||
// if let Some(&TaggedValue::ImaginateSamplingMethod(sampling_method)) = &input.as_non_exposed_value() {
|
||||
// let sampling_methods = ImaginateSamplingMethod::list();
|
||||
// let mut entries = Vec::with_capacity(sampling_methods.len());
|
||||
// for method in sampling_methods {
|
||||
// entries.push(
|
||||
// MenuListEntry::new(format!("{method:?}"))
|
||||
// .label(method.to_string())
|
||||
// .on_update(update_value(move |_| TaggedValue::ImaginateSamplingMethod(method), node_id, sampling_method_index))
|
||||
// .on_commit(commit_value),
|
||||
// );
|
||||
// }
|
||||
// let entries = vec![entries];
|
||||
|
||||
// widgets.extend_from_slice(&[
|
||||
// Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// DropdownInput::new(entries).selected_index(Some(sampling_method as u32)).widget_holder(),
|
||||
// ]);
|
||||
// }
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Algorithm used to generate the image during each sampling step")
|
||||
// };
|
||||
|
||||
// let text_guidance = {
|
||||
// let widgets = number_widget(document_node, node_id, text_guidance_index, "Prompt Guidance", NumberInput::default().min(0.).max(30.), true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// "Amplification of the text prompt's influence over the outcome. At 0, the prompt is entirely ignored.\n\
|
||||
// \n\
|
||||
// Lower values are more creative and exploratory. Higher values are more literal and uninspired.\n\
|
||||
// \n\
|
||||
// This parameter is otherwise known as CFG (classifier-free guidance).",
|
||||
// )
|
||||
// };
|
||||
|
||||
// let text_prompt = {
|
||||
// let widgets = text_area_widget(document_node, node_id, text_index, "Prompt", true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// "Description of the desired image subject and style.\n\
|
||||
// \n\
|
||||
// Include an artist name like \"Rembrandt\" or art medium like \"watercolor\" or \"photography\" to influence the look. List multiple to meld styles.\n\
|
||||
// \n\
|
||||
// To boost (or lessen) the importance of a word or phrase, wrap it in parentheses ending with a colon and a multiplier, for example:\n\
|
||||
// \"Colorless green ideas (sleep:1.3) furiously\"",
|
||||
// )
|
||||
// };
|
||||
// let negative_prompt = {
|
||||
// let widgets = text_area_widget(document_node, node_id, neg_index, "Negative Prompt", true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("A negative text prompt can be used to list things like objects or colors to avoid")
|
||||
// };
|
||||
// let base_image = {
|
||||
// let widgets = bool_widget(document_node, node_id, base_img_index, "Adapt Input Image", CheckboxInput::default().for_label(checkbox_id.clone()), true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Generate an image based upon the bitmap data plugged into this node")
|
||||
// };
|
||||
// let image_creativity = {
|
||||
// let props = NumberInput::default().percentage().disabled(!use_base_image);
|
||||
// let widgets = number_widget(document_node, node_id, img_creativity_index, "Image Creativity", props, true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// "Strength of the artistic liberties allowing changes from the input image. The image is unchanged at 0% and completely different at 100%.\n\
|
||||
// \n\
|
||||
// This parameter is otherwise known as denoising strength.",
|
||||
// )
|
||||
// };
|
||||
|
||||
// let mut layout = vec![
|
||||
// server_status,
|
||||
// progress,
|
||||
// image_controls,
|
||||
// seed,
|
||||
// resolution,
|
||||
// sampling_steps,
|
||||
// sampling_method,
|
||||
// text_guidance,
|
||||
// text_prompt,
|
||||
// negative_prompt,
|
||||
// base_image,
|
||||
// image_creativity,
|
||||
// // layer_mask,
|
||||
// ];
|
||||
|
||||
// // if use_base_image && layer_reference_input_layer_is_some {
|
||||
// // let in_paint = {
|
||||
// // let mut widgets = start_widgets(document_node, node_id, inpaint_index, "Inpaint", FrontendGraphDataType::Boolean, true);
|
||||
|
||||
// // if let Some(& TaggedValue::Bool(in_paint)
|
||||
// //)/ } = &document_node.inputs[inpaint_index].as_non_exposed_value()
|
||||
// // {
|
||||
// // widgets.extend_from_slice(&[
|
||||
// // Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// // RadioInput::new(
|
||||
// // [(true, "Inpaint"), (false, "Outpaint")]
|
||||
// // .into_iter()
|
||||
// // .map(|(paint, name)| RadioEntryData::new(name).label(name).on_update(update_value(move |_| TaggedValue::Bool(paint), node_id, inpaint_index)))
|
||||
// // .collect(),
|
||||
// // )
|
||||
// // .selected_index(Some(1 - in_paint as u32))
|
||||
// // .widget_holder(),
|
||||
// // ]);
|
||||
// // }
|
||||
// // LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// // "Constrain image generation to the interior (inpaint) or exterior (outpaint) of the mask, while referencing the other unchanged parts as context imagery.\n\
|
||||
// // \n\
|
||||
// // An unwanted part of an image can be replaced by drawing around it with a black shape and inpainting with that mask layer.\n\
|
||||
// // \n\
|
||||
// // An image can be uncropped by resizing the Imaginate layer to the target bounds and outpainting with a black rectangle mask matching the original image bounds.",
|
||||
// // )
|
||||
// // };
|
||||
|
||||
// // let blur_radius = {
|
||||
// // let number_props = NumberInput::default().unit(" px").min(0.).max(25.).int();
|
||||
// // let widgets = number_widget(document_node, node_id, mask_blur_index, "Mask Blur", number_props, true);
|
||||
// // LayoutGroup::Row { widgets }.with_tooltip("Blur radius for the mask. Useful for softening sharp edges to blend the masked area with the rest of the image.")
|
||||
// // };
|
||||
|
||||
// // let mask_starting_fill = {
|
||||
// // let mut widgets = start_widgets(document_node, node_id, mask_fill_index, "Mask Starting Fill", FrontendGraphDataType::General, true);
|
||||
|
||||
// // if let Some(& TaggedValue::ImaginateMaskStartingFill(starting_fill)
|
||||
// //)/ } = &document_node.inputs[mask_fill_index].as_non_exposed_value()
|
||||
// // {
|
||||
// // let mask_fill_content_modes = ImaginateMaskStartingFill::list();
|
||||
// // let mut entries = Vec::with_capacity(mask_fill_content_modes.len());
|
||||
// // for mode in mask_fill_content_modes {
|
||||
// // entries.push(MenuListEntry::new(format!("{mode:?}")).label(mode.to_string()).on_update(update_value(move |_| TaggedValue::ImaginateMaskStartingFill(mode), node_id, mask_fill_index)));
|
||||
// // }
|
||||
// // let entries = vec![entries];
|
||||
|
||||
// // widgets.extend_from_slice(&[
|
||||
// // Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
// // DropdownInput::new(entries).selected_index(Some(starting_fill as u32)).widget_holder(),
|
||||
// // ]);
|
||||
// // }
|
||||
// // LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// // "Begin in/outpainting the masked areas using this fill content as the starting input image.\n\
|
||||
// // \n\
|
||||
// // Each option can be visualized by generating with 'Sampling Steps' set to 0.",
|
||||
// // )
|
||||
// // };
|
||||
// // layout.extend_from_slice(&[in_paint, blur_radius, mask_starting_fill]);
|
||||
// // }
|
||||
|
||||
// let improve_faces = {
|
||||
// let widgets = bool_widget(document_node, node_id, faces_index, "Improve Faces", CheckboxInput::default().for_label(checkbox_id.clone()), true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip(
|
||||
// "Postprocess human (or human-like) faces to look subtly less distorted.\n\
|
||||
// \n\
|
||||
// This filter can be used on its own by enabling 'Adapt Input Image' and setting 'Sampling Steps' to 0.",
|
||||
// )
|
||||
// };
|
||||
// let tiling = {
|
||||
// let widgets = bool_widget(document_node, node_id, tiling_index, "Tiling", CheckboxInput::default().for_label(checkbox_id.clone()), true);
|
||||
// LayoutGroup::Row { widgets }.with_tooltip("Generate the image so its edges loop seamlessly to make repeatable patterns or textures")
|
||||
// };
|
||||
// layout.extend_from_slice(&[improve_faces, tiling]);
|
||||
|
||||
// layout
|
||||
// }
|
||||
@@ -9,8 +9,8 @@ use core::borrow::Borrow;
|
||||
use core::f64::consts::{FRAC_PI_2, TAU};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::ClickTargetType;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::renderer::{ClickTarget, ClickTargetType, Quad};
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, VectorData};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
@@ -693,5 +693,5 @@ impl PTZ {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum GroupFolderType {
|
||||
Layer,
|
||||
BooleanOperation(graphene_std::vector::misc::BooleanOperation),
|
||||
BooleanOperation(graphene_std::path_bool::BooleanOperation),
|
||||
}
|
||||
|
||||
@@ -12,8 +12,9 @@ use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::renderer::{ClickTarget, ClickTargetType, Quad};
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
use interpreted_executor::node_registry::NODE_REGISTRY;
|
||||
@@ -6515,6 +6516,12 @@ pub struct NodePersistentMetadata {
|
||||
position: NodePosition,
|
||||
}
|
||||
|
||||
impl NodePersistentMetadata {
|
||||
pub fn new(position: NodePosition) -> Self {
|
||||
Self { position }
|
||||
}
|
||||
}
|
||||
|
||||
/// A layer can either be position as Absolute or in a Stack
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum LayerPosition {
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::{HandleExt, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleId, PointId};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
@@ -0,0 +1,707 @@
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// This file contains lots of hacky code for upgrading old documents to the new format
|
||||
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::text::TypesettingConfig;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"),
|
||||
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
||||
];
|
||||
|
||||
const REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
|
||||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
|
||||
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
||||
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
||||
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
||||
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"),
|
||||
("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"),
|
||||
("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||
("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"),
|
||||
("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"),
|
||||
("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"),
|
||||
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
|
||||
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
|
||||
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
||||
("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
|
||||
(
|
||||
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
|
||||
"graphene_core::vector::generator_nodes::RegularPolygonNode",
|
||||
),
|
||||
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
|
||||
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
|
||||
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
|
||||
("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
|
||||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"),
|
||||
];
|
||||
|
||||
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
|
||||
TEXT_REPLACEMENTS
|
||||
.iter()
|
||||
.fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new))
|
||||
}
|
||||
|
||||
pub fn document_migration_reset_node_definition(document_serialized_content: &str) -> bool {
|
||||
// Upgrade a document being opened to use fresh copies of all nodes
|
||||
if document_serialized_content.contains("node_output_index") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI)
|
||||
if document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode") {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
// Apply string replacements to each node
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
|
||||
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document
|
||||
.network_interface
|
||||
.nested_network(&network_path)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get(node_id)
|
||||
.map(|node| node.implementation.clone())
|
||||
{
|
||||
for (old, new) in REPLACEMENTS {
|
||||
let node_path_without_type_args = protonode_id.name.split('<').next();
|
||||
if node_path_without_type_args == Some(old) {
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
|
||||
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reset_node_definitions_on_open {
|
||||
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
|
||||
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
|
||||
for node_id in &document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<NodeId>>()
|
||||
{
|
||||
if let Some(reference) = document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.get(node_id)
|
||||
.and_then(|node| node.persistent_metadata.reference.as_ref())
|
||||
{
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
|
||||
let default_definition_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
|
||||
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
|
||||
{
|
||||
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
|
||||
}
|
||||
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
|
||||
// Apply upgrades to each node
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
let network_path = &network_path;
|
||||
|
||||
let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else {
|
||||
log::error!("could not get node in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else {
|
||||
log::error!("could not get node metadata for node {node_id} in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||
// TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally.
|
||||
// TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected.
|
||||
// log::error!("could not get reference in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Fill nodes to the format change in #1778
|
||||
if reference == "Fill" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
|
||||
let TaggedValue::FillType(fill_type) = fill_type else { continue };
|
||||
let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue };
|
||||
let TaggedValue::OptionalColor(solid_color) = solid_color else { continue };
|
||||
let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientType(gradient_type) = gradient_type else { continue };
|
||||
let Some(start) = old_inputs[4].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(start) = start else { continue };
|
||||
let Some(end) = old_inputs[5].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(end) = end else { continue };
|
||||
let Some(transform) = old_inputs[6].as_value().cloned() else { continue };
|
||||
let TaggedValue::DAffine2(transform) = transform else { continue };
|
||||
let Some(positions) = old_inputs[7].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientStops(positions) = positions else { continue };
|
||||
|
||||
let fill = match (fill_type, solid_color) {
|
||||
(FillType::Solid, None) => Fill::None,
|
||||
(FillType::Solid, Some(color)) => Fill::Solid(color),
|
||||
(FillType::Gradient, _) => Fill::Gradient(Gradient {
|
||||
stops: positions,
|
||||
gradient_type,
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
}),
|
||||
};
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path);
|
||||
match fill {
|
||||
Fill::None => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
|
||||
let node = document.network_interface.document_node(node_id, network_path).unwrap();
|
||||
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");
|
||||
continue;
|
||||
};
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
let spline_outputs = document
|
||||
.network_interface
|
||||
.outward_wires(network_path)
|
||||
.unwrap()
|
||||
.get(&OutputConnector::node(*node_id, 0))
|
||||
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
|
||||
.clone();
|
||||
|
||||
// Get the node's current position in the graph
|
||||
let Some(node_position) = document.network_interface.position(node_id, network_path) else {
|
||||
log::error!("Could not get position of spline node.");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the "Path" node definition and fill it in with the vector data and default vector modification
|
||||
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist.");
|
||||
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)),
|
||||
]);
|
||||
|
||||
// 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.");
|
||||
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
|
||||
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
|
||||
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
|
||||
let new_path_id = *new_ids.get(&NodeId(1)).unwrap();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 == "Text" && inputs_count != 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), 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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_image_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
|
||||
|
||||
// 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_noise_pattern_node = node_definition.default_node_template();
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Flatten Path").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), 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), old_inputs[1].clone(), 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());
|
||||
}
|
||||
}
|
||||
|
||||
// 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, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, FlipAxis, GroupFolderType};
|
||||
use crate::messages::prelude::*;
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct MenuBarMessageHandler {
|
||||
|
||||
@@ -2,6 +2,7 @@ mod portfolio_message;
|
||||
mod portfolio_message_handler;
|
||||
|
||||
pub mod document;
|
||||
pub mod document_migration;
|
||||
pub mod menu_bar;
|
||||
pub mod spreadsheet;
|
||||
pub mod utility_types;
|
||||
|
||||
@@ -54,9 +54,6 @@ pub enum PortfolioMessage {
|
||||
preview_url: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
// ImaginateCheckServerStatus,
|
||||
// ImaginatePollServerStatus,
|
||||
// ImaginateServerHostname,
|
||||
Import,
|
||||
LoadDocumentResources {
|
||||
document_id: DocumentId,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::document::utility_types::network_interface::{self, InputConnector, OutputConnector};
|
||||
use super::document::utility_types::network_interface;
|
||||
use super::spreadsheet::SpreadsheetMessageHandler;
|
||||
use super::utility_types::{PanelType, PersistentData};
|
||||
use crate::application::generate_uuid;
|
||||
@@ -11,21 +11,17 @@ use crate::messages::frontend::utility_types::FrontendDocumentDetails;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::DocumentMessageData;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use graphene_std::text::Font;
|
||||
use std::vec;
|
||||
|
||||
pub struct PortfolioMessageData<'a> {
|
||||
@@ -332,35 +328,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
// PortfolioMessage::ImaginateCheckServerStatus => {
|
||||
// let server_status = self.persistent_data.imaginate.server_status().clone();
|
||||
// self.persistent_data.imaginate.poll_server_check();
|
||||
// #[cfg(target_arch = "wasm32")]
|
||||
// if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() {
|
||||
// wasm_bindgen_futures::spawn_local(async move {
|
||||
// let () = fut.await;
|
||||
// use wasm_bindgen::prelude::*;
|
||||
|
||||
// #[wasm_bindgen(module = "/../frontend/src/editor.ts")]
|
||||
// extern "C" {
|
||||
// #[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||
// fn inject();
|
||||
// }
|
||||
// inject();
|
||||
// })
|
||||
// }
|
||||
// if &server_status != self.persistent_data.imaginate.server_status() {
|
||||
// responses.add(PropertiesPanelMessage::Refresh);
|
||||
// }
|
||||
// }
|
||||
// PortfolioMessage::ImaginatePollServerStatus => {
|
||||
// self.persistent_data.imaginate.poll_server_check();
|
||||
// responses.add(PropertiesPanelMessage::Refresh);
|
||||
// }
|
||||
PortfolioMessage::EditorPreferences => self.executor.update_editor_preferences(preferences.editor_preferences()),
|
||||
// PortfolioMessage::ImaginateServerHostname => {
|
||||
// self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
|
||||
// }
|
||||
PortfolioMessage::Import => {
|
||||
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
|
||||
responses.add(FrontendMessage::TriggerImport);
|
||||
@@ -429,30 +397,18 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
document_serialized_content,
|
||||
to_front,
|
||||
} => {
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// This big code block contains lots of hacky code for upgrading old documents to the new format
|
||||
|
||||
// Upgrade a document being opened to use fresh copies of all nodes
|
||||
let replace_implementations_from_definition = reset_node_definitions_on_open || document_serialized_content.contains("node_output_index");
|
||||
// Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI)
|
||||
let upgrade_from_before_returning_nested_click_targets =
|
||||
document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode");
|
||||
let upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__");
|
||||
let document_name = document_name.replace("__DO_NOT_UPGRADE__", "");
|
||||
|
||||
const TEXT_REPLACEMENTS: [(&str, &str); 2] = [
|
||||
("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePointsNode"),
|
||||
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
|
||||
];
|
||||
let document_serialized_content = TEXT_REPLACEMENTS
|
||||
.iter()
|
||||
.fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new));
|
||||
// Upgrade the document being opened to use fresh copies of all nodes
|
||||
let reset_node_definitions_on_open = reset_node_definitions_on_open || document_migration_reset_node_definition(&document_serialized_content);
|
||||
// Upgrade the document being opened with string replacements on the original JSON
|
||||
let document_serialized_content = document_migration_string_preprocessing(document_serialized_content);
|
||||
|
||||
// Deserialize the document
|
||||
let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| {
|
||||
document.name.clone_from(&document_name);
|
||||
document
|
||||
});
|
||||
|
||||
// Display an error to the user if the document could not be opened
|
||||
let mut document = match document {
|
||||
Ok(document) => document,
|
||||
Err(e) => {
|
||||
@@ -467,660 +423,14 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||
}
|
||||
};
|
||||
|
||||
const REPLACEMENTS: [(&str, &str); 40] = [
|
||||
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
|
||||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
|
||||
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
||||
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
||||
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
||||
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||
("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"),
|
||||
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
|
||||
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
|
||||
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
||||
("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
|
||||
(
|
||||
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
|
||||
"graphene_core::vector::generator_nodes::RegularPolygonNode",
|
||||
),
|
||||
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
|
||||
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
|
||||
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
|
||||
("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
|
||||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
];
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
|
||||
// Apply upgrades to each node
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
|
||||
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document
|
||||
.network_interface
|
||||
.nested_network(&network_path)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get(node_id)
|
||||
.map(|node| node.implementation.clone())
|
||||
{
|
||||
for (old, new) in REPLACEMENTS {
|
||||
let node_path_without_type_args = protonode_id.name.split('<').next();
|
||||
if node_path_without_type_args == Some(old) {
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
|
||||
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade all old nodes to support editable subgraphs introduced in #1750
|
||||
if replace_implementations_from_definition || upgrade_from_before_returning_nested_click_targets {
|
||||
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
|
||||
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
|
||||
for node_id in &document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<NodeId>>()
|
||||
{
|
||||
if let Some(reference) = document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.get(node_id)
|
||||
.and_then(|node| node.persistent_metadata.reference.as_ref())
|
||||
{
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
|
||||
let default_definition_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
|
||||
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
|
||||
{
|
||||
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
|
||||
}
|
||||
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
|
||||
// Apply upgrades to each node
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
let network_path = &network_path;
|
||||
|
||||
let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else {
|
||||
log::error!("could not get node in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else {
|
||||
log::error!("could not get node metadata for node {node_id} in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||
// TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally.
|
||||
// TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected.
|
||||
// log::error!("could not get reference in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Fill nodes to the format change in #1778
|
||||
if reference == "Fill" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
|
||||
let TaggedValue::FillType(fill_type) = fill_type else { continue };
|
||||
let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue };
|
||||
let TaggedValue::OptionalColor(solid_color) = solid_color else { continue };
|
||||
let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientType(gradient_type) = gradient_type else { continue };
|
||||
let Some(start) = old_inputs[4].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(start) = start else { continue };
|
||||
let Some(end) = old_inputs[5].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(end) = end else { continue };
|
||||
let Some(transform) = old_inputs[6].as_value().cloned() else { continue };
|
||||
let TaggedValue::DAffine2(transform) = transform else { continue };
|
||||
let Some(positions) = old_inputs[7].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientStops(positions) = positions else { continue };
|
||||
|
||||
let fill = match (fill_type, solid_color) {
|
||||
(FillType::Solid, None) => Fill::None,
|
||||
(FillType::Solid, Some(color)) => Fill::Solid(color),
|
||||
(FillType::Gradient, _) => Fill::Gradient(Gradient {
|
||||
stops: positions,
|
||||
gradient_type,
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
}),
|
||||
};
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path);
|
||||
match fill {
|
||||
Fill::None => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
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()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
|
||||
let node = document.network_interface.document_node(node_id, network_path).unwrap();
|
||||
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");
|
||||
continue;
|
||||
};
|
||||
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
let spline_outputs = document
|
||||
.network_interface
|
||||
.outward_wires(network_path)
|
||||
.unwrap()
|
||||
.get(&OutputConnector::node(*node_id, 0))
|
||||
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
|
||||
.clone();
|
||||
|
||||
// Get the node's current position in the graph
|
||||
let Some(node_position) = document.network_interface.position(node_id, network_path) else {
|
||||
log::error!("Could not get position of spline node.");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Get the "Path" node definition and fill it in with the vector data and default vector modification
|
||||
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist.");
|
||||
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)),
|
||||
]);
|
||||
|
||||
// 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.");
|
||||
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
|
||||
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
|
||||
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
|
||||
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
|
||||
let new_path_id = *new_ids.get(&NodeId(1)).unwrap();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 == "Text" && inputs_count != 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), 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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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" && upgrade_from_before_returning_nested_click_targets {
|
||||
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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_image_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
|
||||
|
||||
// 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_noise_pattern_node = node_definition.default_node_template();
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Flatten Path").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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 node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// Upgrade document to the new vector manipulation format introduced in #1676
|
||||
let document_serialized_content = document.serialize_document();
|
||||
if upgrade_vector_manipulation_format && !document_serialized_content.is_empty() {
|
||||
responses.add(FrontendMessage::TriggerUpgradeDocumentToVectorManipulationFormat {
|
||||
document_id,
|
||||
document_name,
|
||||
document_is_auto_saved,
|
||||
document_is_saved,
|
||||
document_serialized_content,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they 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, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Upgrade the document's nodes to be compatible with the latest version
|
||||
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
||||
|
||||
// Set the save state of the document based on what's given to us by the caller to this message
|
||||
document.set_auto_save_state(document_is_auto_saved);
|
||||
document.set_save_state(document_is_saved);
|
||||
|
||||
// Load the document into the portfolio so it opens in the editor
|
||||
self.load_document(document, document_id, responses, to_front);
|
||||
}
|
||||
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
|
||||
|
||||
@@ -4,7 +4,6 @@ use graphene_std::text::FontCache;
|
||||
pub struct PersistentData {
|
||||
pub font_cache: FontCache,
|
||||
pub use_vello: bool,
|
||||
// pub imaginate: ImaginatePersistentData,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
||||
@@ -16,6 +16,4 @@ pub enum PreferencesMessage {
|
||||
ModifyLayout { zoom_with_scroll: bool },
|
||||
GraphWireStyle { style: GraphWireStyle },
|
||||
ViewportZoomWheelRate { rate: f64 },
|
||||
// ImaginateRefreshFrequency { seconds: f64 },
|
||||
// ImaginateServerHostname { hostname: String },
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ use graph_craft::wasm_application_io::EditorPreferences;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct PreferencesMessageHandler {
|
||||
// pub imaginate_server_hostname: String,
|
||||
// pub imaginate_refresh_frequency: f64,
|
||||
pub selection_mode: SelectionMode,
|
||||
pub zoom_with_scroll: bool,
|
||||
pub use_vello: bool,
|
||||
@@ -24,7 +22,6 @@ impl PreferencesMessageHandler {
|
||||
|
||||
pub fn editor_preferences(&self) -> EditorPreferences {
|
||||
EditorPreferences {
|
||||
// imaginate_hostname: self.imaginate_server_hostname.clone(),
|
||||
use_vello: self.use_vello && self.supports_wgpu(),
|
||||
}
|
||||
}
|
||||
@@ -37,8 +34,6 @@ impl PreferencesMessageHandler {
|
||||
impl Default for PreferencesMessageHandler {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// imaginate_server_hostname: EditorPreferences::default().imaginate_hostname,
|
||||
// imaginate_refresh_frequency: 1.,
|
||||
selection_mode: SelectionMode::Touched,
|
||||
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
||||
use_vello: EditorPreferences::default().use_vello,
|
||||
@@ -57,10 +52,6 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
|
||||
*self = deserialized_preferences;
|
||||
|
||||
// TODO: Reenable when Imaginate is restored
|
||||
// responses.add(PortfolioMessage::ImaginateServerHostname);
|
||||
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
|
||||
responses.add(PortfolioMessage::EditorPreferences);
|
||||
responses.add(PortfolioMessage::UpdateVelloPreference);
|
||||
responses.add(PreferencesMessage::ModifyLayout {
|
||||
@@ -101,27 +92,6 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
self.viewport_zoom_wheel_rate = rate;
|
||||
}
|
||||
}
|
||||
// TODO: Reenable when Imaginate is restored (and move back up one line since the auto-formatter doesn't like it in that block)
|
||||
// PreferencesMessage::ImaginateRefreshFrequency { seconds } => {
|
||||
// self.imaginate_refresh_frequency = seconds;
|
||||
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
// responses.add(PortfolioMessage::EditorPreferences);
|
||||
// }
|
||||
// PreferencesMessage::ImaginateServerHostname { hostname } => {
|
||||
// let initial = hostname.clone();
|
||||
// let has_protocol = hostname.starts_with("http://") || hostname.starts_with("https://");
|
||||
// let hostname = if has_protocol { hostname } else { "http://".to_string() + &hostname };
|
||||
// let hostname = if hostname.ends_with('/') { hostname } else { hostname + "/" };
|
||||
|
||||
// if hostname != initial {
|
||||
// refresh_dialog(responses);
|
||||
// }
|
||||
|
||||
// self.imaginate_server_hostname = hostname;
|
||||
// responses.add(PortfolioMessage::ImaginateServerHostname);
|
||||
// responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
// responses.add(PortfolioMessage::EditorPreferences);
|
||||
//}
|
||||
|
||||
responses.add(FrontendMessage::TriggerSavePreferences { preferences: self.clone() });
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ pub use crate::messages::tool::tool_messages::eyedropper_tool::{EyedropperToolMe
|
||||
pub use crate::messages::tool::tool_messages::fill_tool::{FillToolMessage, FillToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::freehand_tool::{FreehandToolMessage, FreehandToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::gradient_tool::{GradientToolMessage, GradientToolMessageDiscriminant};
|
||||
// pub use crate::messages::tool::tool_messages::imaginate_tool::{ImaginateToolMessage, ImaginateToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::navigate_tool::{NavigateToolMessage, NavigateToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::path_tool::{PathToolMessage, PathToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::pen_tool::{PenToolMessage, PenToolMessageDiscriminant};
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
use crate::messages::message::Message;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::PolygonGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::StarGizmoHandler;
|
||||
use glam::DVec2;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// A unified enum wrapper around all available shape-specific gizmo handlers.
|
||||
///
|
||||
/// This abstraction allows `GizmoManager` to interact with different shape gizmos (like Star or Polygon)
|
||||
/// using a common interface without needing to know the specific shape type at compile time.
|
||||
///
|
||||
/// Each variant stores a concrete handler (e.g., `StarGizmoHandler`, `PolygonGizmoHandler`) that implements
|
||||
/// the shape-specific logic for rendering overlays, responding to input, and modifying shape parameters.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum ShapeGizmoHandlers {
|
||||
#[default]
|
||||
None,
|
||||
Star(StarGizmoHandler),
|
||||
Polygon(PolygonGizmoHandler),
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandlers {
|
||||
/// Returns the kind of shape the handler is managing, such as `"star"` or `"polygon"`.
|
||||
/// Used for grouping logic and distinguishing between handler types at runtime.
|
||||
pub fn kind(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Star(_) => "star",
|
||||
Self::Polygon(_) => "polygon",
|
||||
Self::None => "none",
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatches interaction state updates to the corresponding shape-specific handler.
|
||||
pub fn handle_state(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::Polygon(h) => h.handle_state(layer, mouse_position, document, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if any interactive part of the gizmo is currently hovered.
|
||||
pub fn is_any_gizmo_hovered(&self) -> bool {
|
||||
match self {
|
||||
Self::Star(h) => h.is_any_gizmo_hovered(),
|
||||
Self::Polygon(h) => h.is_any_gizmo_hovered(),
|
||||
Self::None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Passes the click interaction to the appropriate gizmo handler if one is hovered.
|
||||
pub fn handle_click(&mut self) {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_click(),
|
||||
Self::Polygon(h) => h.handle_click(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the gizmo state while the user is dragging a handle (e.g., adjusting radius).
|
||||
pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
match self {
|
||||
Self::Star(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::Polygon(h) => h.handle_update(drag_start, document, input, responses),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans up any state used by the gizmo handler.
|
||||
pub fn cleanup(&mut self) {
|
||||
match self {
|
||||
Self::Star(h) => h.cleanup(),
|
||||
Self::Polygon(h) => h.cleanup(),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws overlays like control points or outlines for the shape handled by this gizmo.
|
||||
pub fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
layer: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
match self {
|
||||
Self::Star(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.overlays(document, layer, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws live-updating overlays during drag interactions for the shape handled by this gizmo.
|
||||
pub fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
match self {
|
||||
Self::Star(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::Polygon(h) => h.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context),
|
||||
Self::None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Central manager that coordinates shape gizmo handlers for interactive editing on the canvas.
|
||||
///
|
||||
/// The `GizmoManager` is responsible for detecting which shapes are selected, activating the appropriate
|
||||
/// shape-specific gizmo, and routing user interactions (hover, click, drag) to the correct handler.
|
||||
/// It allows editing multiple shapes of the same type or focusing on a single active shape when a gizmo is hovered.
|
||||
///
|
||||
/// ## Responsibilities:
|
||||
/// - Detect which selected layers support shape gizmos (e.g., stars, polygons)
|
||||
/// - Activate the correct handler and manage state between frames
|
||||
/// - Route click, hover, and drag events to the proper shape gizmo
|
||||
/// - Render overlays and dragging visuals
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct GizmoManager {
|
||||
active_shape_handler: Option<ShapeGizmoHandlers>,
|
||||
layers_handlers: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)>,
|
||||
}
|
||||
|
||||
impl GizmoManager {
|
||||
/// Detects and returns a shape gizmo handler based on the layer type (e.g., star, polygon).
|
||||
///
|
||||
/// Returns `None` if the given layer does not represent a shape with a registered gizmo.
|
||||
pub fn detect_shape_handler(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Option<ShapeGizmoHandlers> {
|
||||
// Star
|
||||
if graph_modification_utils::get_star_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Star(StarGizmoHandler::default()));
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if graph_modification_utils::get_polygon_id(layer, &document.network_interface).is_some() {
|
||||
return Some(ShapeGizmoHandlers::Polygon(PolygonGizmoHandler::default()));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if a gizmo is currently active (hovered or being interacted with).
|
||||
pub fn hovering_over_gizmo(&self) -> bool {
|
||||
self.active_shape_handler.is_some()
|
||||
}
|
||||
|
||||
/// Called every frame to check selected layers and update the active shape gizmo, if hovered.
|
||||
///
|
||||
/// Also groups all shape layers with the same kind of gizmo to support overlays for multi-shape editing.
|
||||
pub fn handle_actions(&mut self, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let mut handlers_layer: Vec<(ShapeGizmoHandlers, Vec<LayerNodeIdentifier>)> = Vec::new();
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
|
||||
if let Some(mut handler) = Self::detect_shape_handler(layer, document) {
|
||||
handler.handle_state(layer, mouse_position, document, responses);
|
||||
let is_hovered = handler.is_any_gizmo_hovered();
|
||||
|
||||
if is_hovered {
|
||||
self.layers_handlers.clear();
|
||||
self.active_shape_handler = Some(handler);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to group this handler with others of the same type
|
||||
if let Some((_, layers)) = handlers_layer.iter_mut().find(|(existing_handler, _)| existing_handler.kind() == handler.kind()) {
|
||||
layers.push(layer);
|
||||
} else {
|
||||
handlers_layer.push((handler, vec![layer]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.layers_handlers = handlers_layer;
|
||||
self.active_shape_handler = None;
|
||||
}
|
||||
|
||||
/// Handles click interactions if a gizmo is active. Returns `true` if a gizmo handled the click.
|
||||
pub fn handle_click(&mut self) -> bool {
|
||||
if let Some(handle) = &mut self.active_shape_handler {
|
||||
handle.handle_click();
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn handle_cleanup(&mut self) {
|
||||
if let Some(handle) = &mut self.active_shape_handler {
|
||||
handle.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
/// Passes drag update data to the active gizmo to update shape parameters live.
|
||||
pub fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if let Some(handle) = &mut self.active_shape_handler {
|
||||
handle.handle_update(drag_start, document, input, responses);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws overlays for the currently active shape gizmo during a drag interaction.
|
||||
pub fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if let Some(handle) = &self.active_shape_handler {
|
||||
handle.dragging_overlays(document, input, shape_editor, mouse_position, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws overlays for either the active gizmo (if hovered) or all grouped selected gizmos.
|
||||
///
|
||||
/// If no single gizmo is active, it renders overlays for all grouped layers with associated handlers.
|
||||
pub fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if let Some(handler) = &self.active_shape_handler {
|
||||
handler.overlays(document, None, input, shape_editor, mouse_position, overlay_context);
|
||||
return;
|
||||
}
|
||||
|
||||
for (handler, selected_layers) in &self.layers_handlers {
|
||||
for layer in selected_layers {
|
||||
handler.overlays(document, Some(*layer), input, shape_editor, mouse_position, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod gizmo_manager;
|
||||
pub mod shape_gizmos;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod number_of_points_dial;
|
||||
pub mod point_radius_handle;
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION, NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::prelude::Responses;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_polygon_parameters, inside_polygon, inside_star, polygon_outline, polygon_vertex_position, star_outline};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum NumberOfPointsDialState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NumberOfPointsDial {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub initial_points: u32,
|
||||
pub handle_state: NumberOfPointsDialState,
|
||||
}
|
||||
|
||||
impl NumberOfPointsDial {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.handle_state = NumberOfPointsDialState::Inactive;
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: NumberOfPointsDialState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn is_hovering(&self) -> bool {
|
||||
self.handle_state == NumberOfPointsDialState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.handle_state == NumberOfPointsDialState::Dragging
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
match &self.handle_state {
|
||||
NumberOfPointsDialState::Inactive => {
|
||||
// Star
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);
|
||||
|
||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = sides;
|
||||
self.update_state(NumberOfPointsDialState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);
|
||||
|
||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = sides;
|
||||
self.update_state(NumberOfPointsDialState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if mouse_position.distance(center) > NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsDialState::Hover) {
|
||||
self.update_state(NumberOfPointsDialState::Inactive);
|
||||
self.layer = None;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(&self, document: &DocumentMessageHandler, layer: Option<LayerNodeIdentifier>, shape_editor: &mut &mut ShapeState, mouse_position: DVec2, overlay_context: &mut OverlayContext) {
|
||||
match &self.handle_state {
|
||||
NumberOfPointsDialState::Inactive => {
|
||||
let Some(layer) = layer else { return };
|
||||
|
||||
// Star
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let radius = radius1.max(radius2);
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||
if closest_segment.layer() == layer {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let point_on_max_radius = star_vertex_position(viewport, 0, sides, radius1, radius2);
|
||||
|
||||
if inside_star(viewport, sides, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||
if closest_segment.layer() == layer {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, sides, radius);
|
||||
|
||||
if inside_polygon(viewport, sides, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging => {
|
||||
let Some(layer) = self.layer else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Get the star's greater radius or polygon's radius, as well as the number of sides
|
||||
let Some((sides, radius)) = extract_star_parameters(Some(layer), document)
|
||||
.map(|(sides, r1, r2)| (sides, r1.max(r2)))
|
||||
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
// Draw either the star or polygon outline
|
||||
star_outline(Some(layer), document, overlay_context);
|
||||
polygon_outline(Some(layer), document, overlay_context);
|
||||
|
||||
self.draw_spokes(center, viewport, sides, radius, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_spokes(&self, center: DVec2, viewport: DAffine2, sides: u32, radius: f64, overlay_context: &mut OverlayContext) {
|
||||
for i in 0..sides {
|
||||
let angle = ((i as f64) * TAU) / (sides as f64);
|
||||
|
||||
let point = viewport.transform_point2(DVec2 {
|
||||
x: radius * angle.sin(),
|
||||
y: -radius * angle.cos(),
|
||||
});
|
||||
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
let end_point = direction * NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH;
|
||||
if matches!(self.handle_state, NumberOfPointsDialState::Hover | NumberOfPointsDialState::Dragging) {
|
||||
overlay_context.line(center, end_point * NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION + center, None, None);
|
||||
} else {
|
||||
overlay_context.line(center, end_point + center, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
||||
let net_delta = (delta.length() / 25.).round() * sign;
|
||||
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
+188
-174
@@ -1,12 +1,15 @@
|
||||
use crate::consts::{COLOR_OVERLAY_RED, GIZMO_HIDE_THRESHOLD, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
|
||||
use crate::consts::GIZMO_HIDE_THRESHOLD;
|
||||
use crate::consts::{COLOR_OVERLAY_RED, POINT_RADIUS_HANDLE_SNAP_THRESHOLD};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::portfolio::document::{overlays::utility_types::OverlayContext, utility_types::network_interface::InputConnector};
|
||||
use crate::messages::prelude::FrontendMessage;
|
||||
use crate::messages::prelude::Responses;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, NodeGraphLayer};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, extract_star_parameters, polygon_vertex_position, star_vertex_position};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{draw_snapping_ticks, extract_polygon_parameters, polygon_outline, polygon_vertex_position, star_outline};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{extract_star_parameters, star_vertex_position};
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
@@ -39,78 +42,71 @@ impl PointRadiusHandle {
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn is_inactive(&self) -> bool {
|
||||
self.handle_state == PointRadiusHandleState::Inactive
|
||||
}
|
||||
|
||||
pub fn hovered(&self) -> bool {
|
||||
self.handle_state == PointRadiusHandleState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging_or_snapped(&self) -> bool {
|
||||
self.handle_state == PointRadiusHandleState::Dragging || matches!(self.handle_state, PointRadiusHandleState::Snapped(_))
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: PointRadiusHandleState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn handle_actions(&mut self, document: &DocumentMessageHandler, mouse_position: DVec2) {
|
||||
pub fn handle_actions(&mut self, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, mouse_position: DVec2, responses: &mut VecDeque<Message>) {
|
||||
match &self.handle_state {
|
||||
PointRadiusHandleState::Inactive => {
|
||||
for layer in document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter(|layer| {
|
||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
||||
}) {
|
||||
// Draw the point handle gizmo for the star shape
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
// Draw the point handle gizmo for the star shape
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
for i in 0..2 * n {
|
||||
let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };
|
||||
let point = star_vertex_position(viewport, i as i32, n, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
for i in 0..2 * sides {
|
||||
let (radius, radius_index) = if i % 2 == 0 { (radius1, 2) } else { (radius2, 3) };
|
||||
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
self.radius_index = radius_index;
|
||||
self.layer = Some(layer);
|
||||
self.point = i;
|
||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||
self.initial_radius = radius;
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
if point.distance(mouse_position) < 5. {
|
||||
self.radius_index = radius_index;
|
||||
self.layer = Some(layer);
|
||||
self.point = i;
|
||||
self.snap_radii = Self::calculate_snap_radii(document, layer, radius_index);
|
||||
self.initial_radius = radius;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the point handle gizmo for the polygon shape
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
// Draw the point handle gizmo for the polygon shape
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
for i in 0..n {
|
||||
let point = polygon_vertex_position(viewport, i as i32, n, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
for i in 0..sides {
|
||||
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
// If the user zooms out so the shape is very small, hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
self.radius_index = 2;
|
||||
self.layer = Some(layer);
|
||||
self.point = i;
|
||||
self.snap_radii.clear();
|
||||
self.initial_radius = radius;
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
|
||||
return;
|
||||
}
|
||||
if point.distance(mouse_position) < 5. {
|
||||
self.radius_index = 2;
|
||||
self.layer = Some(layer);
|
||||
self.point = i;
|
||||
self.snap_radii.clear();
|
||||
self.initial_radius = radius;
|
||||
self.update_state(PointRadiusHandleState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,8 +117,9 @@ impl PointRadiusHandle {
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
||||
// Star
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
||||
self.update_state(PointRadiusHandleState::Inactive);
|
||||
@@ -131,8 +128,9 @@ impl PointRadiusHandle {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let point = polygon_vertex_position(viewport, self.point as i32, n, radius);
|
||||
// Polygon
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let point = polygon_vertex_position(viewport, self.point as i32, sides, radius);
|
||||
|
||||
if matches!(&self.handle_state, PointRadiusHandleState::Hover) && (mouse_position - point).length() > 5. {
|
||||
self.update_state(PointRadiusHandleState::Inactive);
|
||||
@@ -144,85 +142,109 @@ impl PointRadiusHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(&mut self, other_gizmo_active: bool, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, mouse_position: DVec2, overlay_context: &mut OverlayContext) {
|
||||
pub fn overlays(
|
||||
&self,
|
||||
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
match &self.handle_state {
|
||||
PointRadiusHandleState::Inactive => {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
||||
});
|
||||
for layer in layers {
|
||||
if other_gizmo_active {
|
||||
return;
|
||||
}
|
||||
// Draw the point handle gizmo for the star shape
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let Some(layer) = selected_star_layer else { return };
|
||||
|
||||
for i in 0..(2 * n) {
|
||||
let point = star_vertex_position(viewport, i as i32, n, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
// Draw the point handle gizmo for the star shape
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
for i in 0..(2 * sides) {
|
||||
let point = star_vertex_position(viewport, i as i32, sides, radius1, radius2);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
let angle = ((i as f64) * PI) / (n as f64);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the point handle gizmo for the Polygon shape
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
for i in 0..n {
|
||||
let point = polygon_vertex_position(viewport, i as i32, n, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
let angle = ((i as f64) * PI) / (sides as f64);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the point handle gizmo for the Polygon shape
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
for i in 0..sides {
|
||||
let point = polygon_vertex_position(viewport, i as i32, sides, radius);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
if point.distance(mouse_position) < 5. {
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
overlay_context.manipulator_handle(point, false, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PointRadiusHandleState::Dragging | PointRadiusHandleState::Hover => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
let viewport_diagonal = input.viewport_bounds.size().length();
|
||||
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let angle = ((self.point as f64) * PI) / (n as f64);
|
||||
let point = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
||||
// Star
|
||||
if let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let angle = ((self.point as f64) * PI) / (sides as f64);
|
||||
let point = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
let Some(direction) = (point - center).try_normalize() else { return };
|
||||
|
||||
// Draws the ray from the center to the dragging point extending till the viewport
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
star_outline(Some(layer), document, overlay_context);
|
||||
|
||||
// Make the ticks for snapping
|
||||
|
||||
// If dragging to make radius negative don't show the
|
||||
if (mouse_position - center).dot(direction) < 0. {
|
||||
return;
|
||||
}
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Polygon
|
||||
if let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let point = polygon_vertex_position(viewport, self.point as i32, sides, radius);
|
||||
|
||||
let Some(direction) = (point - center).try_normalize() else { return };
|
||||
|
||||
@@ -230,47 +252,33 @@ impl PointRadiusHandle {
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
|
||||
// Makes the tick marks for snapping
|
||||
|
||||
// Only show the snapping ticks if the radius is positive
|
||||
if (mouse_position - center).dot(direction) >= 0. {
|
||||
draw_snapping_ticks(&self.snap_radii, direction, viewport, angle, overlay_context);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let point = polygon_vertex_position(viewport, self.point as i32, n, radius);
|
||||
|
||||
let Some(direction) = (point - center).try_normalize() else { return };
|
||||
|
||||
// Draws the ray from the center to the dragging point and extending until the viewport edge is reached
|
||||
overlay_context.manipulator_handle(point, true, None);
|
||||
overlay_context.line(center, center + direction * viewport_diagonal, None, None);
|
||||
polygon_outline(Some(layer), document, overlay_context);
|
||||
}
|
||||
}
|
||||
PointRadiusHandleState::Snapped(snapping_index) => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return };
|
||||
let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
match snapping_index {
|
||||
// Make a triangle with the previous two points
|
||||
// Make a triangle with previous two points
|
||||
0 => {
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, n, radius1, radius2);
|
||||
let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2);
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 2, sides, radius1, radius2);
|
||||
let outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(before_outer_position, outer_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let l1 = (before_outer_position - outer_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - outer_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (point_position - outer_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - outer_position).try_normalize() else { return };
|
||||
|
||||
let l1 = 0.2 * (before_outer_position - outer_position).length();
|
||||
let new_point = SQRT_2 * l1 * direction + outer_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + outer_position;
|
||||
@@ -280,18 +288,20 @@ impl PointRadiusHandle {
|
||||
overlay_context.line(new_point, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
1 => {
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, n, radius1, radius2);
|
||||
let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, n, radius1, radius2);
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
||||
let before_outer_position = star_vertex_position(viewport, (self.point as i32) - 1, sides, radius1, radius2);
|
||||
|
||||
let after_point_position = star_vertex_position(viewport, (self.point as i32) + 1, sides, radius1, radius2);
|
||||
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(before_outer_position, point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(point_position, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
|
||||
let l1 = (before_outer_position - point_position).length() * 0.2;
|
||||
let Some(l1_direction) = (before_outer_position - point_position).try_normalize() else { return };
|
||||
let Some(l2_direction) = (after_point_position - point_position).try_normalize() else { return };
|
||||
let Some(direction) = (center - point_position).try_normalize() else { return };
|
||||
|
||||
let l1 = 0.2 * (before_outer_position - point_position).length();
|
||||
let new_point = SQRT_2 * l1 * direction + point_position;
|
||||
|
||||
let before_outer_position = l1 * l1_direction + point_position;
|
||||
@@ -301,35 +311,37 @@ impl PointRadiusHandle {
|
||||
overlay_context.line(new_point, after_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
i => {
|
||||
// Use `self.point` as an absolute reference, as it matches the index of the star's vertices starting from 0
|
||||
// Use `self.point` as absolute reference as it matches the index of vertices of the star starting from 0
|
||||
if i % 2 != 0 {
|
||||
// Flipped case
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, n, radius1, radius2);
|
||||
let point_position = star_vertex_position(viewport, self.point as i32, sides, radius1, radius2);
|
||||
let target_index = (1 - (*i as i32)).abs() + (self.point as i32);
|
||||
let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2);
|
||||
let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);
|
||||
|
||||
let mirrored_index = 2 * (self.point as i32) - target_index;
|
||||
let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2);
|
||||
let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(point_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(point_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
} else {
|
||||
let outer_index = (self.point as i32) - 1;
|
||||
let outer_position = star_vertex_position(viewport, outer_index, n, radius1, radius2);
|
||||
let outer_position = star_vertex_position(viewport, outer_index, sides, radius1, radius2);
|
||||
|
||||
// The vertex which is colinear with the point we are dragging and its previous outer vertex
|
||||
let target_index = (self.point as i32) + (*i as i32) - 1;
|
||||
let target_point_position = star_vertex_position(viewport, target_index, n, radius1, radius2);
|
||||
let target_point_position = star_vertex_position(viewport, target_index, sides, radius1, radius2);
|
||||
|
||||
let mirrored_index = 2 * outer_index - target_index;
|
||||
|
||||
let mirrored = star_vertex_position(viewport, mirrored_index, n, radius1, radius2);
|
||||
let mirrored = star_vertex_position(viewport, mirrored_index, sides, radius1, radius2);
|
||||
|
||||
overlay_context.line(outer_position, target_point_position, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
overlay_context.line(outer_position, mirrored, Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
star_outline(Some(layer), document, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,29 +354,32 @@ impl PointRadiusHandle {
|
||||
};
|
||||
|
||||
let other_index = if radius_index == 3 { 2 } else { 3 };
|
||||
|
||||
let Some(&TaggedValue::F64(other_radius)) = node_inputs[other_index].as_value() else {
|
||||
return snap_radii;
|
||||
};
|
||||
let Some(&TaggedValue::U32(n)) = node_inputs[1].as_value() else {
|
||||
let Some(&TaggedValue::U32(sides)) = node_inputs[1].as_value() else {
|
||||
return snap_radii;
|
||||
};
|
||||
|
||||
// Inner radius for 90°
|
||||
let b = FRAC_PI_4 * 3. - PI / (n as f64);
|
||||
let b = FRAC_PI_4 * 3. - PI / (sides as f64);
|
||||
let angle = b.sin();
|
||||
let required_radius = (other_radius / angle) * FRAC_1_SQRT_2;
|
||||
|
||||
snap_radii.push(required_radius);
|
||||
|
||||
// Also add the case where the radius exceeds the other radius (the "flipped" case)
|
||||
// Also push the case when the when it length increases more than the other
|
||||
|
||||
let flipped = other_radius * angle * SQRT_2;
|
||||
|
||||
snap_radii.push(flipped);
|
||||
|
||||
for i in 1..n {
|
||||
let n = n as f64;
|
||||
for i in 1..sides {
|
||||
let sides = sides as f64;
|
||||
let i = i as f64;
|
||||
let denominator = 2. * ((PI * (i - 1.)) / n).cos() * ((PI * i) / n).sin();
|
||||
let numerator = ((2. * PI * i) / n).sin();
|
||||
let denominator = 2. * ((PI * (i - 1.)) / sides).cos() * ((PI * i) / sides).sin();
|
||||
let numerator = ((2. * PI * i) / sides).sin();
|
||||
let factor = numerator / denominator;
|
||||
|
||||
if factor < 0. {
|
||||
@@ -392,33 +407,32 @@ impl PointRadiusHandle {
|
||||
|
||||
// Check if either index is 0 or 1 and prioritize them
|
||||
match (*i_a == 0 || *i_a == 1, *i_b == 0 || *i_b == 1) {
|
||||
(true, false) => std::cmp::Ordering::Less, // a is priority index, b is not
|
||||
(false, true) => std::cmp::Ordering::Greater, // b is priority index, a is not
|
||||
_ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal), // normal comparison
|
||||
// `a` is priority index, `b` is not
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
// `b` is priority index, `a` is not
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
// Normal comparison
|
||||
_ => dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal),
|
||||
}
|
||||
})
|
||||
.map(|(i, rad)| (i, *rad - original_radius))
|
||||
}
|
||||
|
||||
pub fn update_inner_radius(
|
||||
&mut self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
layer: LayerNodeIdentifier,
|
||||
responses: &mut VecDeque<Message>,
|
||||
drag_start: DVec2,
|
||||
) {
|
||||
pub fn update_inner_radius(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let center = transform.transform_point2(DVec2::ZERO);
|
||||
let viewport_transform = document.network_interface.document_metadata().transform_to_viewport(layer);
|
||||
let document_transform = document.network_interface.document_metadata().transform_to_document(layer);
|
||||
let center = viewport_transform.transform_point2(DVec2::ZERO);
|
||||
let radius_index = self.radius_index;
|
||||
|
||||
let original_radius = self.initial_radius;
|
||||
|
||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||
let delta = viewport_transform.inverse().transform_point2(input.mouse.position) - document_transform.inverse().transform_point2(drag_start);
|
||||
let radius = document.metadata().document_to_viewport.transform_point2(drag_start) - center;
|
||||
let projection = delta.project_onto(radius);
|
||||
let sign = radius.dot(delta).signum();
|
||||
@@ -1,12 +1,12 @@
|
||||
pub mod auto_panning;
|
||||
pub mod color_selector;
|
||||
pub mod compass_rose;
|
||||
pub mod gizmos;
|
||||
pub mod graph_modification_utils;
|
||||
pub mod measure;
|
||||
pub mod pivot;
|
||||
pub mod resize;
|
||||
pub mod shape_editor;
|
||||
pub mod shape_gizmos;
|
||||
pub mod shapes;
|
||||
pub mod snapping;
|
||||
pub mod transformation_cage;
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::messages::tool::common_functionality::utility_functions::{find_refit_
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::{HandleId, SegmentId};
|
||||
use graphene_std::vector::{HandleExt, HandleId, SegmentId};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod number_of_points_handle;
|
||||
pub mod point_radius_handle;
|
||||
-241
@@ -1,241 +0,0 @@
|
||||
use crate::consts::{GIZMO_HIDE_THRESHOLD, NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION, NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD};
|
||||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::prelude::Responses;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, FrontendMessage, InputPreprocessorMessageHandler, NodeGraphMessage};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{
|
||||
extract_polygon_parameters, extract_star_parameters, inside_polygon, inside_star, polygon_vertex_position, star_vertex_position,
|
||||
};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum NumberOfPointsHandleState {
|
||||
#[default]
|
||||
Inactive,
|
||||
Hover,
|
||||
Dragging,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct NumberOfPointsHandle {
|
||||
pub layer: Option<LayerNodeIdentifier>,
|
||||
pub initial_points: u32,
|
||||
pub handle_state: NumberOfPointsHandleState,
|
||||
}
|
||||
|
||||
impl NumberOfPointsHandle {
|
||||
pub fn cleanup(&mut self) {
|
||||
self.handle_state = NumberOfPointsHandleState::Inactive;
|
||||
self.layer = None;
|
||||
}
|
||||
|
||||
pub fn update_state(&mut self, state: NumberOfPointsHandleState) {
|
||||
self.handle_state = state;
|
||||
}
|
||||
|
||||
pub fn is_hovering(&self) -> bool {
|
||||
self.handle_state == NumberOfPointsHandleState::Hover
|
||||
}
|
||||
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
self.handle_state == NumberOfPointsHandleState::Dragging
|
||||
}
|
||||
|
||||
pub fn handle_actions(
|
||||
&mut self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) {
|
||||
if input.keyboard.key(Key::Control) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &self.handle_state {
|
||||
NumberOfPointsHandleState::Inactive => {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
||||
});
|
||||
for layer in layers {
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2);
|
||||
|
||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = n;
|
||||
self.update_state(NumberOfPointsHandleState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius);
|
||||
|
||||
if mouse_position.distance(center) < NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.layer = Some(layer);
|
||||
self.initial_points = n;
|
||||
self.update_state(NumberOfPointsHandleState::Hover);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::EWResize });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
let Some((n, radius)) = extract_star_parameters(Some(layer), document)
|
||||
.map(|(n, r1, r2)| (n, r1.max(r2)))
|
||||
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if mouse_position.distance(center) > NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH && matches!(&self.handle_state, NumberOfPointsHandleState::Hover) {
|
||||
self.update_state(NumberOfPointsHandleState::Inactive);
|
||||
self.layer = None;
|
||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Default });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn overlays(
|
||||
&mut self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if input.keyboard.key(Key::Control) {
|
||||
return;
|
||||
}
|
||||
|
||||
match &self.handle_state {
|
||||
NumberOfPointsHandleState::Inactive => {
|
||||
let selected_nodes = document.network_interface.selected_nodes();
|
||||
let layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface).filter(|layer| {
|
||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
||||
});
|
||||
for layer in layers {
|
||||
if let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) {
|
||||
let radius = radius1.max(radius2);
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||
if closest_segment.layer() == layer {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let point_on_max_radius = star_vertex_position(viewport, 0, n, radius1, radius2);
|
||||
|
||||
if inside_star(viewport, n, radius1, radius2, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) {
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, mouse_position, POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD) {
|
||||
if closest_segment.layer() == layer {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let point_on_max_radius = polygon_vertex_position(viewport, 0, n, radius);
|
||||
|
||||
if inside_polygon(viewport, n, radius, mouse_position) && point_on_max_radius.distance(center) > GIZMO_HIDE_THRESHOLD {
|
||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging => {
|
||||
let Some(layer) = self.layer else { return };
|
||||
|
||||
let Some((n, radius)) = extract_star_parameters(Some(layer), document)
|
||||
.map(|(n, r1, r2)| (n, r1.max(r2)))
|
||||
.or_else(|| extract_polygon_parameters(Some(layer), document))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
let center = viewport.transform_point2(DVec2::ZERO);
|
||||
|
||||
self.draw_spokes(center, viewport, n, radius, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_spokes(&self, center: DVec2, viewport: DAffine2, n: u32, radius: f64, overlay_context: &mut OverlayContext) {
|
||||
for i in 0..n {
|
||||
let angle = ((i as f64) * TAU) / (n as f64);
|
||||
|
||||
let point = viewport.transform_point2(DVec2 {
|
||||
x: radius * angle.sin(),
|
||||
y: -radius * angle.cos(),
|
||||
});
|
||||
|
||||
let Some(direction) = (point - center).try_normalize() else { continue };
|
||||
|
||||
// If the user zooms out such that shape is very small hide the gizmo
|
||||
if point.distance(center) < GIZMO_HIDE_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
let end_point = direction * NUMBER_OF_POINTS_HANDLE_SPOKE_LENGTH;
|
||||
if matches!(self.handle_state, NumberOfPointsHandleState::Hover | NumberOfPointsHandleState::Dragging) {
|
||||
overlay_context.line(center, end_point * NUMBER_OF_POINTS_HANDLE_SPOKE_EXTENSION + center, None, None);
|
||||
} else {
|
||||
overlay_context.line(center, end_point + center, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_number_of_sides(&self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>, drag_start: DVec2) {
|
||||
let delta = input.mouse.position - document.metadata().document_to_viewport.transform_point2(drag_start);
|
||||
let sign = (input.mouse.position.x - document.metadata().document_to_viewport.transform_point2(drag_start).x).signum();
|
||||
let net_delta = (delta.length() / 25.).round() * sign;
|
||||
|
||||
let Some(layer) = self.layer else { return };
|
||||
let Some(node_id) = graph_modification_utils::get_star_id(layer, &document.network_interface).or(graph_modification_utils::get_polygon_id(layer, &document.network_interface)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let new_point_count = ((self.initial_points as i32) + (net_delta as i32)).max(3);
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
input: NodeInput::value(TaggedValue::U32(new_point_count as u32), false),
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,98 @@ use super::shape_utility::update_radius_sign;
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
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::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDial;
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::NumberOfPointsDialState;
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandle;
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::PointRadiusHandleState;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::ShapeGizmoHandler;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::polygon_outline;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use glam::DAffine2;
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PolygonGizmoHandler {
|
||||
number_of_points_dial: NumberOfPointsDial,
|
||||
point_radius_handle: PointRadiusHandle,
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for PolygonGizmoHandler {
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);
|
||||
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
if self.number_of_points_dial.is_hovering() {
|
||||
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.point_radius_handle.hovered() {
|
||||
self.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.number_of_points_dial.is_dragging() {
|
||||
self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_polygon_layer: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.number_of_points_dial.overlays(document, selected_polygon_layer, shape_editor, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_polygon_layer, document, input, mouse_position, overlay_context);
|
||||
|
||||
polygon_outline(selected_polygon_layer, document, overlay_context);
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if self.number_of_points_dial.is_dragging() {
|
||||
self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.number_of_points_dial.cleanup();
|
||||
self.point_radius_handle.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Polygon;
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ use crate::messages::message::Message;
|
||||
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::network_interface::InputConnector;
|
||||
use crate::messages::prelude::{DocumentMessageHandler, NodeGraphMessage, Responses};
|
||||
use crate::messages::prelude::{DocumentMessageHandler, InputPreprocessorMessageHandler, NodeGraphMessage, Responses};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::*;
|
||||
@@ -12,7 +13,7 @@ use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::ClickTargetType;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::dvec2_to_point;
|
||||
use kurbo::{BezPath, PathEl, Shape};
|
||||
use std::collections::VecDeque;
|
||||
@@ -70,9 +71,62 @@ impl ShapeType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side
|
||||
pub type ShapeToolModifierKey = [Key; 4];
|
||||
|
||||
/// The `ShapeGizmoHandler` trait defines the interactive behavior and overlay logic for shape-specific tools in the editor.
|
||||
/// A gizmo is a visual handle or control point used to manipulate a shape's properties (e.g., number of sides, radius, angle).
|
||||
pub trait ShapeGizmoHandler {
|
||||
/// Called every frame to update the gizmo's interaction state based on the mouse position and selection.
|
||||
///
|
||||
/// This includes detecting hover states and preparing interaction flags or visual feedback (e.g., highlighting a hovered handle).
|
||||
fn handle_state(&mut self, selected_shape_layers: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>);
|
||||
|
||||
/// Called when a mouse click occurs over the canvas and a gizmo handle is hovered.
|
||||
///
|
||||
/// Used to initiate drag interactions or toggle states on the handle, depending on the tool.
|
||||
/// For example, a hovered "number of points" handle might enter a "Dragging" state.
|
||||
fn handle_click(&mut self);
|
||||
|
||||
/// Called during a drag interaction to update the shape's parameters in real time.
|
||||
///
|
||||
/// For example, a handle might calculate the distance from the drag start to determine a new radius or update the number of points.
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>);
|
||||
|
||||
/// Draws the static or hover-dependent overlays associated with the gizmo.
|
||||
///
|
||||
/// These overlays include visual indicators like shape outlines, control points, and hover highlights.
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_shape_layers: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
);
|
||||
|
||||
/// Draws overlays specifically during a drag operation.
|
||||
///
|
||||
/// Used to give real-time visual feedback based on drag progress, such as showing the updated shape preview or snapping guides.
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
);
|
||||
|
||||
/// Returns `true` if any handle or control point in the gizmo is currently being hovered.
|
||||
fn is_any_gizmo_hovered(&self) -> bool;
|
||||
|
||||
/// Resets or clears any internal state maintained by the gizmo when it is no longer active.
|
||||
///
|
||||
/// For example, dragging states or hover flags should be cleared to avoid visual glitches when switching tools or shapes.
|
||||
fn cleanup(&mut self);
|
||||
}
|
||||
|
||||
/// Center, Lock Ratio, Lock Angle, Snap Angle, Increase/Decrease Side
|
||||
pub fn update_radius_sign(end: DVec2, start: DVec2, layer: LayerNodeIdentifier, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
let sign_num = if end[1] > start[1] { 1. } else { -1. };
|
||||
let new_layer = NodeGraphLayer::new(layer, &document.network_interface);
|
||||
@@ -154,19 +208,22 @@ pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the node input values of Star
|
||||
/// Extract the node input values of Star.
|
||||
/// Returns an option of (sides, radius1, radius2).
|
||||
pub fn extract_star_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64, f64)> {
|
||||
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Star")?;
|
||||
|
||||
let (Some(&TaggedValue::U32(n)), Some(&TaggedValue::F64(outer)), Some(&TaggedValue::F64(inner))) = (node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value())
|
||||
let (Some(&TaggedValue::U32(sides)), Some(&TaggedValue::F64(radius_1)), Some(&TaggedValue::F64(radius_2))) =
|
||||
(node_inputs.get(1)?.as_value(), node_inputs.get(2)?.as_value(), node_inputs.get(3)?.as_value())
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some((n, outer, inner))
|
||||
Some((sides, radius_1, radius_2))
|
||||
}
|
||||
|
||||
/// Extract the node input values of Polygon
|
||||
/// Extract the node input values of Polygon.
|
||||
/// Returns an option of (sides, radius).
|
||||
pub fn extract_polygon_parameters(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler) -> Option<(u32, f64)> {
|
||||
let node_inputs = NodeGraphLayer::new(layer?, &document.network_interface).find_node_inputs("Regular Polygon")?;
|
||||
|
||||
@@ -188,7 +245,7 @@ pub fn star_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radiu
|
||||
})
|
||||
}
|
||||
|
||||
/// Calculate the viewport position of as a polygon vertex given its index
|
||||
/// Calculate the viewport position of a polygon vertex given its index
|
||||
pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, radius: f64) -> DVec2 {
|
||||
let angle = ((vertex_index as f64) * TAU) / (n as f64);
|
||||
|
||||
@@ -198,49 +255,37 @@ pub fn polygon_vertex_position(viewport: DAffine2, vertex_index: i32, n: u32, ra
|
||||
})
|
||||
}
|
||||
|
||||
/// Outlines the geometric shape made by the Star node
|
||||
pub fn star_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
let mut anchors = Vec::new();
|
||||
let Some((n, radius1, radius2)) = extract_star_parameters(Some(layer), document) else { return };
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
for i in 0..2 * n {
|
||||
let angle = ((i as f64) * PI) / (n as f64);
|
||||
let radius = if i % 2 == 0 { radius1 } else { radius2 };
|
||||
|
||||
let point = DVec2 {
|
||||
x: radius * angle.sin(),
|
||||
y: -radius * angle.cos(),
|
||||
};
|
||||
|
||||
anchors.push(point);
|
||||
}
|
||||
|
||||
let subpath = [ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))];
|
||||
overlay_context.outline(subpath.iter(), viewport, None);
|
||||
}
|
||||
|
||||
/// Outlines the geometric shape made by the Polygon node
|
||||
pub fn polygon_outline(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
let mut anchors = Vec::new();
|
||||
|
||||
let Some((n, radius)) = extract_polygon_parameters(Some(layer), document) else {
|
||||
/// Outlines the geometric shape made by star-node
|
||||
pub fn star_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
let Some(layer) = layer else { return };
|
||||
let Some((sides, radius1, radius2)) = extract_star_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
for i in 0..2 * n {
|
||||
let angle = ((i as f64) * TAU) / (n as f64);
|
||||
|
||||
let point = DVec2 {
|
||||
x: radius * angle.sin(),
|
||||
y: -radius * angle.cos(),
|
||||
};
|
||||
let points = sides as u64;
|
||||
let diameter: f64 = radius1 * 2.;
|
||||
let inner_diameter = radius2 * 2.;
|
||||
|
||||
anchors.push(point);
|
||||
}
|
||||
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter))];
|
||||
|
||||
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::from_anchors_linear(anchors, true))];
|
||||
overlay_context.outline(subpath.iter(), viewport, None);
|
||||
}
|
||||
|
||||
/// Outlines the geometric shape made by polygon-node
|
||||
pub fn polygon_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
let Some(layer) = layer else { return };
|
||||
let Some((sides, radius)) = extract_polygon_parameters(Some(layer), document) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
let points = sides as u64;
|
||||
let radius: f64 = radius * 2.;
|
||||
|
||||
let subpath: Vec<ClickTargetType> = vec![ClickTargetType::Subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius))];
|
||||
|
||||
overlay_context.outline(subpath.iter(), viewport, None);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ use super::shape_utility::{ShapeToolModifierKey, update_radius_sign};
|
||||
use super::*;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
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::network_interface::{InputConnector, NodeTemplate};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::number_of_points_dial::{NumberOfPointsDial, NumberOfPointsDialState};
|
||||
use crate::messages::tool::common_functionality::gizmos::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeGizmoHandler, star_outline};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use core::f64;
|
||||
use glam::DAffine2;
|
||||
@@ -12,6 +17,81 @@ use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct StarGizmoHandler {
|
||||
number_of_points_dial: NumberOfPointsDial,
|
||||
point_radius_handle: PointRadiusHandle,
|
||||
}
|
||||
|
||||
impl ShapeGizmoHandler for StarGizmoHandler {
|
||||
fn is_any_gizmo_hovered(&self) -> bool {
|
||||
self.number_of_points_dial.is_hovering() || self.point_radius_handle.hovered()
|
||||
}
|
||||
|
||||
fn handle_state(&mut self, selected_star_layer: LayerNodeIdentifier, mouse_position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
self.number_of_points_dial.handle_actions(selected_star_layer, mouse_position, document, responses);
|
||||
self.point_radius_handle.handle_actions(selected_star_layer, document, mouse_position, responses);
|
||||
}
|
||||
|
||||
fn handle_click(&mut self) {
|
||||
if self.number_of_points_dial.is_hovering() {
|
||||
self.number_of_points_dial.update_state(NumberOfPointsDialState::Dragging);
|
||||
return;
|
||||
}
|
||||
|
||||
if self.point_radius_handle.hovered() {
|
||||
self.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_update(&mut self, drag_start: DVec2, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
if self.number_of_points_dial.is_dragging() {
|
||||
self.number_of_points_dial.update_number_of_sides(document, input, responses, drag_start);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.update_inner_radius(document, input, responses, drag_start);
|
||||
}
|
||||
}
|
||||
|
||||
fn overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
selected_star_layer: Option<LayerNodeIdentifier>,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
self.number_of_points_dial.overlays(document, selected_star_layer, shape_editor, mouse_position, overlay_context);
|
||||
self.point_radius_handle.overlays(selected_star_layer, document, input, mouse_position, overlay_context);
|
||||
|
||||
star_outline(selected_star_layer, document, overlay_context);
|
||||
}
|
||||
|
||||
fn dragging_overlays(
|
||||
&self,
|
||||
document: &DocumentMessageHandler,
|
||||
input: &InputPreprocessorMessageHandler,
|
||||
shape_editor: &mut &mut ShapeState,
|
||||
mouse_position: DVec2,
|
||||
overlay_context: &mut OverlayContext,
|
||||
) {
|
||||
if self.number_of_points_dial.is_dragging() {
|
||||
self.number_of_points_dial.overlays(document, None, shape_editor, mouse_position, overlay_context);
|
||||
}
|
||||
|
||||
if self.point_radius_handle.is_dragging_or_snapped() {
|
||||
self.point_radius_handle.overlays(None, document, input, mouse_position, overlay_context);
|
||||
}
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
self.number_of_points_dial.cleanup();
|
||||
self.point_radius_handle.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Star;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::misc::*;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use bezier_rs::Bezier;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{FontCache, load_face};
|
||||
use graphene_std::vector::{HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
|
||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||
pub fn should_extend(
|
||||
|
||||
@@ -48,17 +48,17 @@ pub enum ToolMessage {
|
||||
// Relight(RelightToolMessage),
|
||||
// // #[child]
|
||||
// Detail(DetailToolMessage),
|
||||
// #[child]
|
||||
// Imaginate(ImaginateToolMessage),
|
||||
|
||||
// Messages
|
||||
|
||||
// General tools
|
||||
ActivateToolSelect,
|
||||
ActivateToolArtboard,
|
||||
ActivateToolNavigate,
|
||||
ActivateToolEyedropper,
|
||||
ActivateToolFill,
|
||||
ActivateToolGradient,
|
||||
|
||||
// Vector tools
|
||||
ActivateToolPath,
|
||||
ActivateToolPen,
|
||||
ActivateToolFreehand,
|
||||
@@ -68,10 +68,9 @@ pub enum ToolMessage {
|
||||
ActivateToolShapeEllipse,
|
||||
ActivateToolShape,
|
||||
ActivateToolText,
|
||||
|
||||
// Raster tools
|
||||
ActivateToolBrush,
|
||||
// ActivateToolImaginate,
|
||||
//
|
||||
|
||||
ActivateTool {
|
||||
tool_type: ToolType,
|
||||
},
|
||||
|
||||
@@ -82,7 +82,6 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
responses.add(ShapeToolMessage::HideShapeTypeWidget(true));
|
||||
responses.add(ShapeToolMessage::SetShape(shape));
|
||||
}
|
||||
// ToolMessage::ActivateToolImaginate => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Imaginate }),
|
||||
ToolMessage::ActivateTool { tool_type } => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let old_tool = tool_data.active_tool_type.get_tool();
|
||||
@@ -340,7 +339,6 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
||||
ActivateToolText,
|
||||
|
||||
ActivateToolBrush,
|
||||
// ActivateToolImaginate,
|
||||
|
||||
SelectRandomPrimaryColor,
|
||||
ResetColors,
|
||||
|
||||
@@ -416,7 +416,7 @@ mod test_freehand {
|
||||
editor
|
||||
.handle_message(GraphOperationMessage::TransformSet {
|
||||
layer: artboard,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)),
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
})
|
||||
@@ -424,14 +424,14 @@ mod test_freehand {
|
||||
|
||||
editor.select_tool(ToolType::Freehand).await;
|
||||
|
||||
let mouse_points = [DVec2::new(150.0, 100.0), DVec2::new(200.0, 150.0), DVec2::new(250.0, 130.0), DVec2::new(300.0, 170.0)];
|
||||
let mouse_points = [DVec2::new(150., 100.), DVec2::new(200., 150.), DVec2::new(250., 130.), DVec2::new(300., 170.)];
|
||||
|
||||
// Expected points that will actually be captured by the tool
|
||||
let expected_captured_points = &mouse_points[1..];
|
||||
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
|
||||
|
||||
let vector_data_list = get_vector_data(&mut editor).await;
|
||||
verify_path_points(&vector_data_list, expected_captured_points, 1.0).expect("Path points verification failed");
|
||||
verify_path_points(&vector_data_list, expected_captured_points, 1.).expect("Path points verification failed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -439,7 +439,7 @@ mod test_freehand {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
|
||||
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
||||
let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||
|
||||
editor.select_tool(ToolType::Freehand).await;
|
||||
|
||||
@@ -491,7 +491,7 @@ mod test_freehand {
|
||||
|
||||
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
|
||||
|
||||
let extension_points = [DVec2::new(400.0, 200.0), DVec2::new(500.0, 100.0)];
|
||||
let extension_points = [DVec2::new(400., 200.), DVec2::new(500., 100.)];
|
||||
|
||||
let layer_node_id = {
|
||||
let document = editor.active_document();
|
||||
@@ -558,7 +558,7 @@ mod test_freehand {
|
||||
|
||||
editor.select_tool(ToolType::Freehand).await;
|
||||
|
||||
let initial_points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
||||
let initial_points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||
|
||||
let first_point = initial_points[0];
|
||||
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||
@@ -599,7 +599,7 @@ mod test_freehand {
|
||||
})
|
||||
.await;
|
||||
|
||||
let second_path_points = [DVec2::new(400.0, 100.0), DVec2::new(500.0, 200.0), DVec2::new(600.0, 100.0)];
|
||||
let second_path_points = [DVec2::new(400., 100.), DVec2::new(500., 200.), DVec2::new(600., 100.)];
|
||||
|
||||
let first_second_point = second_path_points[0];
|
||||
editor.move_mouse(first_second_point.x, first_second_point.y, ModifierKeys::SHIFT, MouseKeys::empty()).await;
|
||||
@@ -677,12 +677,12 @@ mod test_freehand {
|
||||
|
||||
editor.select_tool(ToolType::Freehand).await;
|
||||
|
||||
let custom_line_weight = 5.0;
|
||||
let custom_line_weight = 5.;
|
||||
editor
|
||||
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight))))
|
||||
.await;
|
||||
|
||||
let points = [DVec2::new(100.0, 100.0), DVec2::new(200.0, 200.0), DVec2::new(300.0, 100.0)];
|
||||
let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||
|
||||
let first_point = points[0];
|
||||
editor.move_mouse(first_point.x, first_point.y, ModifierKeys::empty(), MouseKeys::empty()).await;
|
||||
|
||||
@@ -718,7 +718,7 @@ mod test_gradient {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
|
||||
|
||||
editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;
|
||||
|
||||
@@ -727,7 +727,7 @@ mod test_gradient {
|
||||
editor
|
||||
.handle_message(GraphOperationMessage::TransformSet {
|
||||
layer: selected_layer,
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10.0, -5.0)),
|
||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(1.5, 0.8), 0.3, DVec2::new(10., -5.)),
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
})
|
||||
@@ -803,7 +803,7 @@ mod test_gradient {
|
||||
stops.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
||||
|
||||
let positions: Vec<f64> = stops.iter().map(|(pos, _)| *pos).collect();
|
||||
assert_stops_at_positions(&positions, &[0.0, 0.5, 1.0], 0.1);
|
||||
assert_stops_at_positions(&positions, &[0., 0.5, 1.], 0.1);
|
||||
|
||||
let middle_color = stops[1].1.to_rgba8_srgb();
|
||||
|
||||
@@ -843,7 +843,7 @@ mod test_gradient {
|
||||
|
||||
// Check positions are now correctly ordered
|
||||
let updated_positions: Vec<f64> = updated_stops.iter().map(|(pos, _)| *pos).collect();
|
||||
assert_stops_at_positions(&updated_positions, &[0.0, 0.8, 1.0], 0.1);
|
||||
assert_stops_at_positions(&updated_positions, &[0., 0.8, 1.], 0.1);
|
||||
|
||||
// Colors should maintain their associations with the stop points
|
||||
assert_eq!(updated_stops[0].1.to_rgba8_srgb(), Color::BLUE.to_rgba8_srgb());
|
||||
@@ -877,7 +877,7 @@ mod test_gradient {
|
||||
let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
||||
|
||||
// Use helper function to verify positions
|
||||
assert_stops_at_positions(&positions, &[0.0, 0.25, 0.75, 1.0], 0.05);
|
||||
assert_stops_at_positions(&positions, &[0., 0.25, 0.75, 1.], 0.05);
|
||||
|
||||
// Select the stop at position 0.75 and delete it
|
||||
let position2 = DVec2::new(75., 0.);
|
||||
@@ -903,7 +903,7 @@ mod test_gradient {
|
||||
let final_positions: Vec<f64> = final_gradient.stops.iter().map(|(pos, _)| *pos).collect();
|
||||
|
||||
// Verify final positions with helper function
|
||||
assert_stops_at_positions(&final_positions, &[0.0, 0.25, 1.0], 0.05);
|
||||
assert_stops_at_positions(&final_positions, &[0., 0.25, 1.], 0.05);
|
||||
|
||||
// Additional verification that 0.75 stop is gone
|
||||
assert!(!final_positions.iter().any(|pos| (pos - 0.75).abs() < 0.05), "Stop at position 0.75 should have been deleted");
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
use super::tool_prelude::*;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ImaginateTool {
|
||||
fsm_state: ImaginateToolFsmState,
|
||||
tool_data: ImaginateToolData,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Imaginate)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum ImaginateToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
Resize { center: Key, lock_ratio: Key },
|
||||
}
|
||||
|
||||
impl LayoutHolder for ImaginateTool {
|
||||
fn layout(&self) -> Layout {
|
||||
Layout::WidgetLayout(WidgetLayout::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ImaginateTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
match self.fsm_state {
|
||||
ImaginateToolFsmState::Ready => actions!(ImaginateToolMessageDiscriminant;
|
||||
DragStart,
|
||||
),
|
||||
ImaginateToolFsmState::Drawing => actions!(ImaginateToolMessageDiscriminant;
|
||||
DragStop,
|
||||
Abort,
|
||||
Resize,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolMetadata for ImaginateTool {
|
||||
fn icon_name(&self) -> String {
|
||||
"RasterImaginateTool".into()
|
||||
}
|
||||
fn tooltip(&self) -> String {
|
||||
"Imaginate Tool".into()
|
||||
}
|
||||
fn tool_type(&self) -> crate::messages::tool::utility_types::ToolType {
|
||||
ToolType::Imaginate
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolTransition for ImaginateTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(ImaginateToolMessage::Abort.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
enum ImaginateToolFsmState {
|
||||
#[default]
|
||||
Ready,
|
||||
Drawing,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ImaginateToolData {
|
||||
data: Resize,
|
||||
}
|
||||
|
||||
impl Fsm for ImaginateToolFsmState {
|
||||
type ToolData = ImaginateToolData;
|
||||
type ToolOptions = ();
|
||||
|
||||
fn transition(
|
||||
self,
|
||||
event: ToolMessage,
|
||||
tool_data: &mut Self::ToolData,
|
||||
ToolActionHandlerData { document, input, .. }: &mut ToolActionHandlerData,
|
||||
_tool_options: &Self::ToolOptions,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Self {
|
||||
let shape_data = &mut tool_data.data;
|
||||
|
||||
let ToolMessage::Imaginate(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
|
||||
shape_data.start(document, input);
|
||||
// responses.add(DocumentMessage::AddTransaction);
|
||||
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId::new(), &document.network_interface));
|
||||
responses.add(DocumentMessage::DeselectAllLayers);
|
||||
|
||||
// // Utility function to offset the position of each consecutive node
|
||||
// let mut pos = 8;
|
||||
// let mut next_pos = || {
|
||||
// pos += 8;
|
||||
// DocumentNodeMetadata::position((pos, 4))
|
||||
// };
|
||||
|
||||
// // Get the node type for the Transform and Imaginate nodes
|
||||
// let Some(transform_node_type) = resolve_document_node_type("Transform") else {
|
||||
// warn!("Transform node should be in registry");
|
||||
// return ImaginateToolFsmState::Drawing;
|
||||
// };
|
||||
// let imaginate_node_type = &*IMAGINATE_NODE;
|
||||
|
||||
// // Give them a unique ID
|
||||
// let transform_node_id = NodeId(100);
|
||||
//let imaginate_node_id = NodeId(101);
|
||||
|
||||
// Create the network based on the Input -> Output passthrough default network
|
||||
// let mut network = new_image_network(16, imaginate_node_id);
|
||||
|
||||
// // Insert the nodes into the default network
|
||||
// network.insert_node(
|
||||
// transform_node_id,
|
||||
// transform_node_type.to_document_node_default_inputs([Some(NodeInput::node(NodeId(0), 0))], next_pos()),
|
||||
// );
|
||||
// network.insert_node(
|
||||
// imaginate_node_id,
|
||||
// imaginate_node_type.to_document_node_default_inputs([Some(NodeInput::node(transform_node_id, 0))], next_pos()),
|
||||
// );
|
||||
// responses.add(NodeGraphMessage::ShiftNode { node_id: imaginate_node_id });
|
||||
|
||||
// // Add a layer with a frame to the document
|
||||
// responses.add(Operation::AddFrame {
|
||||
// path: shape_data.layer.unwrap().to_path(),
|
||||
// insert_index: -1,
|
||||
// transform: DAffine2::ZERO.to_cols_array(),
|
||||
// network,
|
||||
// });
|
||||
|
||||
ImaginateToolFsmState::Drawing
|
||||
}
|
||||
(state, ImaginateToolMessage::Resize { center, lock_ratio }) => {
|
||||
let message = shape_data.calculate_transform(document, input, center, lock_ratio, true);
|
||||
responses.try_add(message);
|
||||
|
||||
state
|
||||
}
|
||||
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::DragStop) => {
|
||||
input.mouse.finish_transaction(shape_data.viewport_drag_start(document), responses);
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
ImaginateToolFsmState::Ready
|
||||
}
|
||||
(ImaginateToolFsmState::Drawing, ImaginateToolMessage::Abort) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
|
||||
shape_data.cleanup(responses);
|
||||
|
||||
ImaginateToolFsmState::Ready
|
||||
}
|
||||
(_, ImaginateToolMessage::Abort) => ImaginateToolFsmState::Ready,
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
let hint_data = match self {
|
||||
ImaginateToolFsmState::Ready => HintData(vec![HintGroup(vec![
|
||||
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Repaint Frame"),
|
||||
HintInfo::keys([Key::Shift], "Constrain Square").prepend_plus(),
|
||||
HintInfo::keys([Key::Alt], "From Center").prepend_plus(),
|
||||
])]),
|
||||
ImaginateToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::keys([Key::Shift], "Constrain Square"), HintInfo::keys([Key::Alt], "From Center")])]),
|
||||
};
|
||||
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
||||
fn update_cursor(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::Crosshair });
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ pub mod eyedropper_tool;
|
||||
pub mod fill_tool;
|
||||
pub mod freehand_tool;
|
||||
pub mod gradient_tool;
|
||||
// pub mod imaginate_tool;
|
||||
pub mod navigate_tool;
|
||||
pub mod path_tool;
|
||||
pub mod pen_tool;
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::{HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||
use std::vec;
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ use crate::messages::tool::common_functionality::utility_functions::{resize_boun
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DMat2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::misc::BooleanOperation;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -6,13 +6,13 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
|
||||
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::graph_modification_utils::{self, NodeGraphLayer};
|
||||
use crate::messages::tool::common_functionality::gizmos::gizmo_manager::GizmoManager;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::common_functionality::shape_gizmos::number_of_points_handle::{NumberOfPointsHandle, NumberOfPointsHandleState};
|
||||
use crate::messages::tool::common_functionality::shape_gizmos::point_radius_handle::{PointRadiusHandle, PointRadiusHandleState};
|
||||
use crate::messages::tool::common_functionality::shapes::line_shape::{LineToolData, clicked_on_line_endpoints};
|
||||
use crate::messages::tool::common_functionality::shapes::polygon_shape::Polygon;
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, polygon_outline, star_outline, transform_cage_overlays};
|
||||
use crate::messages::tool::common_functionality::shapes::shape_utility::{ShapeToolModifierKey, ShapeType, anchor_overlays, transform_cage_overlays};
|
||||
use crate::messages::tool::common_functionality::shapes::star_shape::Star;
|
||||
use crate::messages::tool::common_functionality::shapes::{Ellipse, Line, Rectangle};
|
||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapTypeConfiguration};
|
||||
@@ -22,6 +22,8 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::misc::ArcType;
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ShapeTool {
|
||||
@@ -36,6 +38,7 @@ pub struct ShapeToolOptions {
|
||||
stroke: ToolColorOptions,
|
||||
vertices: u32,
|
||||
shape_type: ShapeType,
|
||||
arc_type: ArcType,
|
||||
}
|
||||
|
||||
impl Default for ShapeToolOptions {
|
||||
@@ -44,8 +47,9 @@ impl Default for ShapeToolOptions {
|
||||
line_weight: DEFAULT_STROKE_WIDTH,
|
||||
fill: ToolColorOptions::new_secondary(),
|
||||
stroke: ToolColorOptions::new_primary(),
|
||||
shape_type: ShapeType::Polygon,
|
||||
vertices: 5,
|
||||
shape_type: ShapeType::Polygon,
|
||||
arc_type: ArcType::Open,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +64,7 @@ pub enum ShapeOptionsUpdate {
|
||||
WorkingColors(Option<Color>, Option<Color>),
|
||||
Vertices(u32),
|
||||
ShapeType(ShapeType),
|
||||
ArcType(ArcType),
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Shape)]
|
||||
@@ -195,6 +200,9 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTo
|
||||
ShapeOptionsUpdate::Vertices(vertices) => {
|
||||
self.options.vertices = vertices;
|
||||
}
|
||||
ShapeOptionsUpdate::ArcType(arc_type) => {
|
||||
self.options.arc_type = arc_type;
|
||||
}
|
||||
}
|
||||
|
||||
self.fsm_state.update_hints(responses);
|
||||
@@ -217,8 +225,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTo
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::DraggingLineEndpoints
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
||||
| ShapeToolFsmState::DraggingStarNumberPointHandle
|
||||
| ShapeToolFsmState::ModifyingGizmo
|
||||
| ShapeToolFsmState::SkewingBounds { .. } => {
|
||||
actions!(ShapeToolMessageDiscriminant;
|
||||
DragStop,
|
||||
@@ -263,12 +270,9 @@ pub enum ShapeToolFsmState {
|
||||
Ready(ShapeType),
|
||||
Drawing(ShapeType),
|
||||
|
||||
// Line shape-specific
|
||||
// Gizmos
|
||||
DraggingLineEndpoints,
|
||||
|
||||
// Star shape-specific
|
||||
DraggingStarInnerRadius,
|
||||
DraggingStarNumberPointHandle,
|
||||
ModifyingGizmo,
|
||||
|
||||
// Transform cage
|
||||
ResizingBounds,
|
||||
@@ -306,9 +310,8 @@ pub struct ShapeToolData {
|
||||
// Current shape which is being drawn
|
||||
current_shape: ShapeType,
|
||||
|
||||
// Gizmo data
|
||||
pub point_radius_handle: PointRadiusHandle,
|
||||
pub number_of_points_handle: NumberOfPointsHandle,
|
||||
// Gizmos
|
||||
gizmo_manger: GizmoManager,
|
||||
}
|
||||
|
||||
impl ShapeToolData {
|
||||
@@ -324,26 +327,6 @@ impl ShapeToolData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn outlines(&self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
|
||||
if let Some(layer) = self.number_of_points_handle.layer.or(self.point_radius_handle.layer) {
|
||||
star_outline(layer, document, overlay_context);
|
||||
polygon_outline(layer, document, overlay_context);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: apply to all selected visible & unlocked star layers
|
||||
for layer in document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
.filter(|layer| {
|
||||
graph_modification_utils::get_star_id(*layer, &document.network_interface).is_some() || graph_modification_utils::get_polygon_id(*layer, &document.network_interface).is_some()
|
||||
}) {
|
||||
star_outline(layer, document, overlay_context);
|
||||
polygon_outline(layer, document, overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Fsm for ShapeToolFsmState {
|
||||
@@ -382,30 +365,24 @@ impl Fsm for ShapeToolFsmState {
|
||||
.map(|pos| document.metadata().document_to_viewport.transform_point2(pos))
|
||||
.unwrap_or(input.mouse.position);
|
||||
let is_resizing_or_rotating = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. } | ShapeToolFsmState::RotatingBounds);
|
||||
let dragging_start_gizmos = matches!(self, Self::DraggingStarInnerRadius);
|
||||
|
||||
if matches!(self, ShapeToolFsmState::DraggingStarInnerRadius | Self::DraggingStarNumberPointHandle | Self::Ready(_)) && !input.keyboard.key(Key::Control) {
|
||||
// Manage state handling of the number of point gizmos
|
||||
tool_data.number_of_points_handle.handle_actions(document, input, mouse_position, &mut overlay_context, responses);
|
||||
|
||||
// Manage state handling of point radius handle gizmo
|
||||
tool_data.point_radius_handle.handle_actions(document, mouse_position);
|
||||
|
||||
tool_data.number_of_points_handle.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
tool_data
|
||||
.point_radius_handle
|
||||
.overlays(tool_data.number_of_points_handle.layer.is_some(), document, input, mouse_position, &mut overlay_context);
|
||||
tool_data.outlines(document, &mut overlay_context);
|
||||
if matches!(self, Self::Ready(_)) && !input.keyboard.key(Key::Control) {
|
||||
tool_data.gizmo_manger.handle_actions(mouse_position, document, responses);
|
||||
tool_data.gizmo_manger.overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
|
||||
let hovered = tool_data.number_of_points_handle.is_hovering() || tool_data.number_of_points_handle.is_dragging() || !tool_data.point_radius_handle.is_inactive();
|
||||
let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });
|
||||
if matches!(self, ShapeToolFsmState::ModifyingGizmo) && !input.keyboard.key(Key::Control) {
|
||||
tool_data.gizmo_manger.dragging_overlays(document, input, shape_editor, mouse_position, &mut overlay_context);
|
||||
}
|
||||
|
||||
if !is_resizing_or_rotating && !dragging_start_gizmos && !hovered && !modifying_transform_cage {
|
||||
let modifying_transform_cage = matches!(self, ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::RotatingBounds | ShapeToolFsmState::SkewingBounds { .. });
|
||||
let hovering_over_gizmo = tool_data.gizmo_manger.hovering_over_gizmo();
|
||||
|
||||
if !is_resizing_or_rotating && !matches!(self, ShapeToolFsmState::ModifyingGizmo) && !modifying_transform_cage && !hovering_over_gizmo {
|
||||
tool_data.data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
}
|
||||
|
||||
if modifying_transform_cage {
|
||||
if modifying_transform_cage && !matches!(self, ShapeToolFsmState::ModifyingGizmo) {
|
||||
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
||||
}
|
||||
|
||||
@@ -418,7 +395,9 @@ impl Fsm for ShapeToolFsmState {
|
||||
return self;
|
||||
}
|
||||
|
||||
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
||||
if !hovering_over_gizmo {
|
||||
transform_cage_overlays(document, tool_data, &mut overlay_context);
|
||||
}
|
||||
|
||||
let dragging_bounds = tool_data
|
||||
.bounding_box_manager
|
||||
@@ -430,10 +409,10 @@ impl Fsm for ShapeToolFsmState {
|
||||
let edges = bounds.check_selected_edges(input.mouse.position);
|
||||
let is_skewing = matches!(self, ShapeToolFsmState::SkewingBounds { .. });
|
||||
let is_near_square = edges.is_some_and(|hover_edge| bounds.over_extended_edge_midpoint(input.mouse.position, hover_edge));
|
||||
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating) {
|
||||
if is_skewing || (dragging_bounds && is_near_square && !is_resizing_or_rotating && !hovering_over_gizmo) {
|
||||
bounds.render_skew_gizmos(&mut overlay_context, tool_data.skew_edge);
|
||||
}
|
||||
if !is_skewing && dragging_bounds {
|
||||
if !is_skewing && dragging_bounds && !hovering_over_gizmo {
|
||||
if let Some(edges) = edges {
|
||||
tool_data.skew_edge = bounds.get_closest_edge(edges, input.mouse.position);
|
||||
}
|
||||
@@ -559,28 +538,9 @@ impl Fsm for ShapeToolFsmState {
|
||||
|
||||
tool_data.line_data.drag_current = mouse_pos;
|
||||
|
||||
// Check if dragging the inner vertices of a star
|
||||
if tool_data.point_radius_handle.hovered() {
|
||||
tool_data.last_mouse_position = mouse_pos;
|
||||
tool_data.point_radius_handle.update_state(PointRadiusHandleState::Dragging);
|
||||
|
||||
// Always store it in document space
|
||||
if tool_data.gizmo_manger.handle_click() {
|
||||
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
return ShapeToolFsmState::DraggingStarInnerRadius;
|
||||
}
|
||||
|
||||
// Check if dragging the number of points handle of a star or polygon
|
||||
if tool_data.number_of_points_handle.is_hovering() {
|
||||
tool_data.last_mouse_position = mouse_pos;
|
||||
tool_data.number_of_points_handle.update_state(NumberOfPointsHandleState::Dragging);
|
||||
|
||||
// Always store it in document space
|
||||
tool_data.data.drag_start = document.metadata().document_to_viewport.inverse().transform_point2(mouse_pos);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
return ShapeToolFsmState::DraggingStarNumberPointHandle;
|
||||
return ShapeToolFsmState::ModifyingGizmo;
|
||||
}
|
||||
|
||||
// If clicked on endpoints of a selected line, drag its endpoints
|
||||
@@ -653,13 +613,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
tool_options.fill.apply_fill(layer, responses);
|
||||
}
|
||||
ShapeType::Line => {
|
||||
tool_data.line_data.angle = 0.;
|
||||
tool_data.line_data.weight = tool_options.line_weight;
|
||||
tool_data.line_data.editing_layer = Some(layer);
|
||||
}
|
||||
}
|
||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||
|
||||
tool_options.stroke.apply_stroke(tool_options.line_weight, layer, responses);
|
||||
tool_data.data.layer = Some(layer);
|
||||
|
||||
ShapeToolFsmState::Drawing(tool_data.current_shape)
|
||||
@@ -695,23 +655,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::DraggingStarInnerRadius, ShapeToolMessage::PointerMove(..)) => {
|
||||
if let Some(layer) = tool_data.point_radius_handle.layer {
|
||||
tool_data.point_radius_handle.update_inner_radius(document, input, layer, responses, tool_data.data.drag_start);
|
||||
tool_data.last_mouse_position = input.mouse.position;
|
||||
}
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
tool_data.gizmo_manger.handle_update(tool_data.data.drag_start, document, input, responses);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ShapeToolFsmState::DraggingStarInnerRadius
|
||||
}
|
||||
(ShapeToolFsmState::DraggingStarNumberPointHandle, ShapeToolMessage::PointerMove(..)) => {
|
||||
tool_data.number_of_points_handle.update_number_of_sides(document, input, responses, tool_data.data.drag_start);
|
||||
|
||||
tool_data.last_mouse_position = input.mouse.position;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ShapeToolFsmState::DraggingStarNumberPointHandle
|
||||
ShapeToolFsmState::ModifyingGizmo
|
||||
}
|
||||
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
@@ -773,12 +723,12 @@ impl Fsm for ShapeToolFsmState {
|
||||
.and_then(|bounding_box| bounding_box.check_selected_edges(input.mouse.position))
|
||||
.is_some();
|
||||
|
||||
let cursor = tool_data
|
||||
.bounding_box_manager
|
||||
.as_ref()
|
||||
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
|
||||
let cursor = tool_data.bounding_box_manager.as_ref().map_or(MouseCursorIcon::Crosshair, |bounds| {
|
||||
let cursor = bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge));
|
||||
if cursor == MouseCursorIcon::Default { MouseCursorIcon::Crosshair } else { cursor }
|
||||
});
|
||||
|
||||
if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && tool_data.point_radius_handle.is_inactive() && !all_selected_layers_line {
|
||||
if tool_data.cursor != cursor && !input.keyboard.key(Key::Control) && !all_selected_layers_line {
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
}
|
||||
@@ -811,15 +761,13 @@ impl Fsm for ShapeToolFsmState {
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::SkewingBounds { .. }
|
||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
||||
| ShapeToolFsmState::DraggingStarNumberPointHandle,
|
||||
| ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolMessage::DragStop,
|
||||
) => {
|
||||
input.mouse.finish_transaction(tool_data.data.drag_start, responses);
|
||||
tool_data.data.cleanup(responses);
|
||||
|
||||
tool_data.number_of_points_handle.cleanup();
|
||||
tool_data.point_radius_handle.cleanup();
|
||||
tool_data.gizmo_manger.handle_cleanup();
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
@@ -837,17 +785,14 @@ impl Fsm for ShapeToolFsmState {
|
||||
| ShapeToolFsmState::ResizingBounds
|
||||
| ShapeToolFsmState::RotatingBounds
|
||||
| ShapeToolFsmState::SkewingBounds { .. }
|
||||
| ShapeToolFsmState::DraggingStarInnerRadius
|
||||
| ShapeToolFsmState::DraggingStarNumberPointHandle,
|
||||
| ShapeToolFsmState::ModifyingGizmo,
|
||||
ShapeToolMessage::Abort,
|
||||
) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.data.cleanup(responses);
|
||||
tool_data.line_data.dragging_endpoint = None;
|
||||
|
||||
// Reset gizmo state
|
||||
tool_data.number_of_points_handle.cleanup();
|
||||
tool_data.point_radius_handle.cleanup();
|
||||
tool_data.gizmo_manger.handle_cleanup();
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
bounds.original_transforms.clear();
|
||||
@@ -952,9 +897,7 @@ impl Fsm for ShapeToolFsmState {
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
HintGroup(vec![HintInfo::keys([Key::Control], "Unlock Slide")]),
|
||||
]),
|
||||
ShapeToolFsmState::DraggingStarInnerRadius | ShapeToolFsmState::DraggingStarNumberPointHandle => {
|
||||
HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])])
|
||||
}
|
||||
ShapeToolFsmState::ModifyingGizmo => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||
};
|
||||
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
|
||||
@@ -656,15 +656,15 @@ mod test_spline_tool {
|
||||
editor.new_document().await;
|
||||
|
||||
// Zooming the viewport
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2.0 }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 2. }).await;
|
||||
|
||||
// Selecting the spline tool
|
||||
editor.select_tool(ToolType::Spline).await;
|
||||
|
||||
// Adding points by clicking at different positions
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||
|
||||
// Finish the spline
|
||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
@@ -686,7 +686,7 @@ mod test_spline_tool {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
@@ -697,15 +697,15 @@ mod test_spline_tool {
|
||||
let mut editor = EditorTestUtils::create();
|
||||
editor.new_document().await;
|
||||
|
||||
let pan_amount = DVec2::new(200.0, 150.0);
|
||||
let pan_amount = DVec2::new(200., 150.);
|
||||
editor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;
|
||||
|
||||
editor.select_tool(ToolType::Spline).await;
|
||||
|
||||
// Add points by clicking at different positions
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||
|
||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
|
||||
@@ -726,7 +726,7 @@ mod test_spline_tool {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
@@ -738,12 +738,12 @@ mod test_spline_tool {
|
||||
editor.new_document().await;
|
||||
|
||||
// Tilt/rotate the viewport (45 degrees)
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45.0_f64.to_radians() }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45_f64.to_radians() }).await;
|
||||
editor.select_tool(ToolType::Spline).await;
|
||||
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||
|
||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
|
||||
@@ -764,7 +764,7 @@ mod test_spline_tool {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
@@ -777,14 +777,14 @@ mod test_spline_tool {
|
||||
|
||||
// Applying multiple transformations
|
||||
editor.handle_message(NavigationMessage::CanvasZoomSet { zoom_factor: 1.5 }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100.0, 75.0) }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30.0_f64.to_radians() }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasPan { delta: DVec2::new(100., 75.) }).await;
|
||||
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 30_f64.to_radians() }).await;
|
||||
|
||||
editor.select_tool(ToolType::Spline).await;
|
||||
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100.0, 50.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150.0, 100.0), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(50., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(100., 50.), ModifierKeys::empty()).await;
|
||||
editor.click_tool(ToolType::Spline, MouseKeys::LEFT, DVec2::new(150., 100.), ModifierKeys::empty()).await;
|
||||
|
||||
editor.handle_message(SplineToolMessage::Confirm).await;
|
||||
if let Err(e) = editor.eval_graph().await {
|
||||
@@ -803,7 +803,7 @@ mod test_spline_tool {
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
||||
// Expected points in viewport coordinates
|
||||
let expected_points = vec![DVec2::new(50.0, 50.0), DVec2::new(100.0, 50.0), DVec2::new(150.0, 100.0)];
|
||||
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
|
||||
|
||||
// Assert all points are correctly positioned
|
||||
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
|
||||
|
||||
@@ -1021,8 +1021,8 @@ mod test_transform_layer {
|
||||
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
||||
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
||||
|
||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
|
||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
|
||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x);
|
||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1047,8 +1047,8 @@ mod test_transform_layer {
|
||||
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
|
||||
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
|
||||
|
||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
|
||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
|
||||
assert!((scale_x - 2.).abs() < 0.1, "Expected scale factor X of 2, got: {}", scale_x);
|
||||
assert!((scale_y - 2.).abs() < 0.1, "Expected scale factor Y of 2, got: {}", scale_y);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -363,7 +363,6 @@ pub enum ToolType {
|
||||
Patch,
|
||||
Detail,
|
||||
Relight,
|
||||
Imaginate,
|
||||
Frame,
|
||||
}
|
||||
|
||||
@@ -418,9 +417,6 @@ fn list_tools_in_groups() -> Vec<Vec<ToolAvailability>> {
|
||||
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Patch, "RasterPatchTool").tooltip("Coming Soon: Patch Tool")),
|
||||
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Detail, "RasterDetailTool").tooltip("Coming Soon: Detail Tool (D)")),
|
||||
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Relight, "RasterRelightTool").tooltip("Coming Soon: Relight Tool (O)")),
|
||||
// TODO: Fix and reenable Imaginate tool
|
||||
// ToolAvailability::Available(Box::<imaginate_tool::ImaginateTool>::default()),
|
||||
ToolAvailability::ComingSoon(ToolEntry::new(ToolType::Heal, "RasterImaginateTool").tooltip("Coming Soon: Imaginate Tool")),
|
||||
],
|
||||
]
|
||||
}
|
||||
@@ -450,7 +446,6 @@ pub fn tool_message_to_tool_type(tool_message: &ToolMessage) -> ToolType {
|
||||
// ToolMessage::Patch(_) => ToolType::Patch,
|
||||
// ToolMessage::Detail(_) => ToolType::Detail,
|
||||
// ToolMessage::Relight(_) => ToolType::Relight,
|
||||
// ToolMessage::Imaginate(_) => ToolType::Imaginate,
|
||||
_ => panic!("Conversion from ToolMessage to ToolType impossible because the given ToolMessage does not have a matching ToolType. Got: {tool_message:?}"),
|
||||
}
|
||||
}
|
||||
@@ -483,7 +478,6 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis
|
||||
// ToolType::Patch => ToolMessageDiscriminant::ActivateToolPatch,
|
||||
// ToolType::Detail => ToolMessageDiscriminant::ActivateToolDetail,
|
||||
// ToolType::Relight => ToolMessageDiscriminant::ActivateToolRelight,
|
||||
// ToolType::Imaginate => ToolMessageDiscriminant::ActivateToolImaginate,
|
||||
_ => panic!("Conversion from ToolType to ToolMessage impossible because the given ToolType does not have a matching ToolMessage. Got: {tool_type:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,9 +299,6 @@ impl NodeGraphExecutor {
|
||||
}
|
||||
}
|
||||
}
|
||||
// NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
|
||||
// responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));
|
||||
// }
|
||||
NodeGraphUpdate::CompilationResponse(execution_response) => {
|
||||
let CompilationResponse { node_graph_errors, result } = execution_response;
|
||||
let type_delta = match result {
|
||||
|
||||
@@ -45,6 +45,9 @@ pub struct NodeRuntime {
|
||||
/// Which node is inspected and which monitor node is used (if any) for the current execution
|
||||
inspect_state: Option<InspectState>,
|
||||
|
||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||
substitutions: HashMap<String, DocumentNode>,
|
||||
|
||||
// TODO: Remove, it doesn't need to be persisted anymore
|
||||
/// The current renders of the thumbnails for layer nodes.
|
||||
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
||||
@@ -120,6 +123,8 @@ impl NodeRuntime {
|
||||
node_graph_errors: Vec::new(),
|
||||
monitor_nodes: Vec::new(),
|
||||
|
||||
substitutions: preprocessor::generate_node_substitutions(),
|
||||
|
||||
thumbnail_renders: Default::default(),
|
||||
vector_modify: Default::default(),
|
||||
inspect_state: None,
|
||||
@@ -221,11 +226,16 @@ impl NodeRuntime {
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_network(&mut self, graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
|
||||
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
|
||||
if cfg!(not(test)) {
|
||||
preprocessor::expand_network(&mut graph, &self.substitutions);
|
||||
}
|
||||
|
||||
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
|
||||
|
||||
// We assume only one output
|
||||
assert_eq!(scoped_network.exports.len(), 1, "Graph with multiple outputs not yet handled");
|
||||
|
||||
let c = Compiler {};
|
||||
let proto_network = match c.compile_single(scoped_network) {
|
||||
Ok(network) => network,
|
||||
|
||||
Reference in New Issue
Block a user