From 356e88fd20ad1eee79731b9f1183fdd4093f88af Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 16:52:23 +0000 Subject: [PATCH 01/16] Selecting multiple nodes --- frontend/src/components/panels/NodeGraph.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index 53726e3da6..d11bedf828 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -438,11 +438,17 @@ export default defineComponent({ const nodeId = node?.getAttribute("data-node") || undefined; if (nodeId) { const id = BigInt(nodeId); - this.editor.instance.selectNodes(new BigUint64Array([id])); - this.selected = [id]; + if (e.shiftKey || e.ctrlKey) { + if (this.selected.includes(id)) this.selected.splice(this.selected.lastIndexOf(id), 1); + else this.selected.push(id); + } else { + this.selected = [id]; + } + + this.editor.instance.selectNodes(new BigUint64Array(this.selected)); } else { - this.editor.instance.selectNodes(new BigUint64Array([])); this.selected = []; + this.editor.instance.selectNodes(new BigUint64Array(this.selected)); const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el; graphDiv?.setPointerCapture(e.pointerId); From 5df59553d12811ea8d27c0da73192c0d1a379963 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 17:12:00 +0000 Subject: [PATCH 02/16] Improve logs --- .../src/messages/portfolio/portfolio_message_handler.rs | 8 ++++++-- node-graph/graph-craft/src/node_registry.rs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 9b7b04e01a..4c7ffbb9bb 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -437,7 +437,7 @@ impl MessageHandler(result).unwrap(); diff --git a/node-graph/graph-craft/src/node_registry.rs b/node-graph/graph-craft/src/node_registry.rs index d00e8fa7a6..5571a362bf 100644 --- a/node-graph/graph-craft/src/node_registry.rs +++ b/node-graph/graph-craft/src/node_registry.rs @@ -243,6 +243,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ (NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), |proto_node, stack| { if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args { stack.push_fn(move |nodes| { + info!("Map image Depending upon id {:?}", operation_node_id); let operation_node = nodes.get(operation_node_id[0] as usize).unwrap(); let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node); let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node)); From 48b99a85561289a1c3d8794d84aa6a0dcc8cf872 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 17:12:38 +0000 Subject: [PATCH 03/16] Log bad types in dyn any --- Cargo.lock | 1 + libraries/dyn-any/Cargo.toml | 2 ++ libraries/dyn-any/src/lib.rs | 21 +++++++++++++++++---- node-graph/graph-craft/Cargo.toml | 2 +- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e980d94f09..6f4dfee959 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,6 +223,7 @@ name = "dyn-any" version = "0.2.1" dependencies = [ "dyn-any-derive", + "log", ] [[package]] diff --git a/libraries/dyn-any/Cargo.toml b/libraries/dyn-any/Cargo.toml index e83bbfd938..df07548afb 100644 --- a/libraries/dyn-any/Cargo.toml +++ b/libraries/dyn-any/Cargo.toml @@ -13,9 +13,11 @@ documentation = "https://docs.rs/dyn-any" [dependencies] dyn-any-derive = { path = "derive", version = "0.2.0", optional = true } +log = { version = "0.4", optional = true } [features] derive = ["dyn-any-derive"] +log-bad-types = ["log"] [package.metadata.docs.rs] all-features = true diff --git a/libraries/dyn-any/src/lib.rs b/libraries/dyn-any/src/lib.rs index 387e09f75c..6461aa04fa 100644 --- a/libraries/dyn-any/src/lib.rs +++ b/libraries/dyn-any/src/lib.rs @@ -50,12 +50,18 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom for dyn DynAny<'a> + 'a { pub trait DynAny<'a> { fn type_id(&self) -> TypeId; + #[cfg(feature = "log-bad-types")] + fn type_name(&self) -> &'static str; } impl<'a, T: StaticType> DynAny<'a> for T { fn type_id(&self) -> std::any::TypeId { std::any::TypeId::of::() } + #[cfg(feature = "log-bad-types")] + fn type_name(&self) -> &'static str { + std::any::type_name::() + } } pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> { if i.type_id() == std::any::TypeId::of::<::Static>() { @@ -74,7 +80,11 @@ pub fn downcast<'a, V: StaticType>(i: Box + 'a>) -> Option let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V; Some(unsafe { Box::from_raw(ptr) }) } else { - // TODO: add log error + #[cfg(feature = "log-bad-types")] + { + log::error!("Tried to downcast a {} to a {}", DynAny::type_name(i.as_ref()), core::any::type_name::()); + } + if type_id == std::any::TypeId::of::<&dyn DynAny<'static>>() { panic!("downcast error: type_id == std::any::TypeId::of::>()"); } @@ -192,9 +202,12 @@ impl_type!(Option,Result,Cell,UnsafeCell,RefCell,MaybeUninit crate::StaticType for Box { type Static = Box<::Static>; } -fn test() { - let foo = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>)); - //let bar = &foo as &dyn DynAny<'static>; +#[test] +fn test_tuple_of_boxes() { + let tuple = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>)); + let dyn_any = &tuple as &dyn DynAny; + assert_eq!(&1, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().0).unwrap()); + assert_eq!(&2, downcast_ref(*downcast_ref::<(Box<&dyn DynAny>, Box<&dyn DynAny>)>(dyn_any).unwrap().1).unwrap()); } macro_rules! impl_tuple { diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index 47d1f85f16..e1a789cbb2 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" [dependencies] graphene-core = { path = "../gcore", features = ["async", "std"] } graphene-std = { path = "../gstd" } -dyn-any = { path = "../../libraries/dyn-any" } +dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] } num-traits = "0.2" borrow_stack = { path = "../borrow_stack" } dyn-clone = "1.0" From 710824473ffb3d46ec1ccaaa3364a9f35df4569d Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 17:13:08 +0000 Subject: [PATCH 04/16] Add (broken) node links --- .../document/node_graph/node_graph_message.rs | 7 +---- .../node_graph/node_graph_message_handler.rs | 30 +++++++++++-------- frontend/wasm/src/editor_api.rs | 2 +- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index f8e195c1b5..6465d02da6 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -7,16 +7,11 @@ use graph_craft::proto::NodeIdentifier; #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub enum NodeGraphMessage { // Messages - AddLink { - from: NodeId, - to: NodeId, - to_index: usize, - }, CloseNodeGraph, ConnectNodesByLink { output_node: u64, input_node: u64, - input_node_connector_index: u32, + input_node_connector_index: usize, }, CreateNode { // Having the caller generate the id means that we don't have to return it. This can be a random u64. diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index ec6b0acf78..9f1510a205 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -204,19 +204,6 @@ impl MessageHandler) { #[remain::sorted] match message { - NodeGraphMessage::AddLink { from, to, to_index } => { - log::debug!("Connect primary output from node {from} to input of index {to_index} on node {to}."); - - if let Some(network) = self.get_active_network_mut(document) { - if let Some(to) = network.nodes.get_mut(&to) { - // Extend number of inputs if not already large enough - if to_index >= to.inputs.len() { - to.inputs.extend(((to.inputs.len() - 1)..to_index).map(|_| NodeInput::Network)); - } - to.inputs[to_index] = NodeInput::Node(from); - } - } - } NodeGraphMessage::CloseNodeGraph => { if let Some(_old_layer_path) = self.layer_path.take() { info!("Closing node graph"); @@ -231,6 +218,23 @@ impl MessageHandler { log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}."); + + let Some(network) = self.get_active_network_mut(document) else { + error!("No network"); + return; + }; + let Some(input_node) = network.nodes.get_mut(&input_node) else { + error!("No to"); + return; + }; + // Extend number of inputs if not already large enough + if input_node_connector_index >= input_node.inputs.len() { + input_node.inputs.extend(((input_node.inputs.len() - 1)..input_node_connector_index).map(|_| NodeInput::Network)); + } + input_node.inputs[input_node_connector_index] = NodeInput::Node(output_node); + + info!("Inputs: {:?}", input_node.inputs); + Self::send_graph(network, responses); } NodeGraphMessage::CreateNode { node_id, diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index d16a357bb2..28abf894ce 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -541,7 +541,7 @@ impl JsEditorHandle { /// Notifies the backend that the user connected a node's primary output to one of another node's inputs #[wasm_bindgen(js_name = connectNodesByLink)] - pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: u32) { + pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) { let message = NodeGraphMessage::ConnectNodesByLink { output_node, input_node, From 6daf2b2165df03ca3a407f1c3b298944bc85a728 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 17:13:23 +0000 Subject: [PATCH 05/16] New topological sort --- node-graph/graph-craft/src/proto.rs | 58 +++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 9ba63b8289..d683bb6544 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::collections::HashSet; use crate::document::value; use crate::document::NodeId; @@ -165,7 +166,37 @@ impl ProtoNetwork { edges } - // Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm + // Based on https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search + // This approach excludes nodes that are not connected + pub fn topological_sort(&self) -> Vec { + let mut sorted = Vec::new(); + let inwards_edges = self.collect_inwards_edges(); + fn visit(node_id: NodeId, temp_marks: &mut HashSet, sorted: &mut Vec, inwards_edges: &HashMap>) { + if sorted.contains(&node_id) { + return; + }; + if temp_marks.contains(&node_id) { + panic!("Cycle detected"); + } + info!("Visiting {node_id}"); + + if let Some(dependencies) = inwards_edges.get(&node_id) { + temp_marks.insert(node_id); + for &dependant in dependencies { + visit(dependant, temp_marks, sorted, inwards_edges); + } + temp_marks.remove(&node_id); + } + sorted.push(node_id); + } + assert!(self.nodes.iter().any(|(id, _)| *id == self.output), "Output id {} does not exist", self.output); + visit(self.output, &mut HashSet::new(), &mut sorted, &inwards_edges); + + info!("Sorted order {sorted:?}"); + sorted + } + + /*// Based on https://en.wikipedia.org/wiki/Topological_sorting#Kahn's_algorithm pub fn topological_sort(&self) -> Vec { let mut sorted = Vec::new(); let outwards_edges = self.collect_outwards_edges(); @@ -187,21 +218,18 @@ impl ProtoNetwork { } } } + info!("Sorted order {sorted:?}"); sorted - } + }*/ pub fn reorder_ids(&mut self) { let order = self.topological_sort(); - let lookup = self - .nodes + info!("Order {order:?}"); + self.nodes = order .iter() - .map(|(id, _)| (*id, order.iter().position(|x| x == id).unwrap() as u64)) - .collect::>(); - self.nodes.sort_by_key(|(id, _)| lookup.get(id).unwrap()); - self.nodes.iter_mut().for_each(|(id, node)| { - node.map_ids(|id| *lookup.get(&id).unwrap()); - *id = *lookup.get(id).unwrap() - }); + .map(|id| self.nodes.swap_remove(self.nodes.iter().position(|(test_id, _)| test_id == id).unwrap())) + .collect(); + assert_eq!(order.len(), self.nodes.len()); } } @@ -217,6 +245,14 @@ mod test { inputs: vec![10], output: 1, nodes: [ + ( + 7, + ProtoNode { + identifier: "id".into(), + input: ProtoNodeInput::Node(11), + construction_args: ConstructionArgs::Nodes(vec![]), + }, + ), ( 1, ProtoNode { From 666554c84a607924df2d447e1942395fea879c2c Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 18:11:05 +0000 Subject: [PATCH 06/16] Fix reorder ids function --- node-graph/graph-craft/src/proto.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index d683bb6544..b0665d3fd1 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -1,5 +1,4 @@ -use std::collections::HashMap; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use crate::document::value; use crate::document::NodeId; @@ -224,10 +223,16 @@ impl ProtoNetwork { pub fn reorder_ids(&mut self) { let order = self.topological_sort(); + // Map of node ids to indexes (which become the node ids as they are inserted into the borrow stack) + let lookup: HashMap<_, _> = order.iter().enumerate().map(|(pos, id)| (*id, pos as NodeId)).collect(); info!("Order {order:?}"); self.nodes = order .iter() - .map(|id| self.nodes.swap_remove(self.nodes.iter().position(|(test_id, _)| test_id == id).unwrap())) + .map(|id| { + let mut node = self.nodes.swap_remove(self.nodes.iter().position(|(test_id, _)| test_id == id).unwrap()).1; + node.map_ids(|id| *lookup.get(&id).unwrap()); + (*lookup.get(id).unwrap(), node) + }) .collect(); assert_eq!(order.len(), self.nodes.len()); } From 0b78f8fafba59abae0e759122be0b79d5966e3e1 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 18:56:13 +0000 Subject: [PATCH 07/16] Input and output node --- .../portfolio/portfolio_message_handler.rs | 2 +- graphene/src/layers/nodegraph_layer.rs | 29 +++++++++++++++---- node-graph/graph-craft/src/node_registry.rs | 20 +++++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 4c7ffbb9bb..4796fa3555 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -495,7 +495,7 @@ impl MessageHandler responses.push_back( DialogMessage::DisplayDialogError { - title: "Failed to update image".to_string(), + title: "Failed to update node graph".to_string(), description, } .into(), diff --git a/graphene/src/layers/nodegraph_layer.rs b/graphene/src/layers/nodegraph_layer.rs index 170af30d80..c34bbc6450 100644 --- a/graphene/src/layers/nodegraph_layer.rs +++ b/graphene/src/layers/nodegraph_layer.rs @@ -6,6 +6,7 @@ use crate::layers::text_layer::FontCache; use crate::LayerId; use glam::{DAffine2, DMat2, DVec2}; +use graph_craft::proto::Type; use kurbo::{Affine, BezPath, Shape as KurboShape}; use serde::{Deserialize, Serialize}; use std::fmt::Write; @@ -157,11 +158,19 @@ impl Default for NodeGraphFrameLayer { Self { mime: String::new(), network: NodeNetwork { - inputs: vec![2, 1], - output: 2, + inputs: vec![0], + output: 4, nodes: [ ( 0, + DocumentNode { + name: "Input".into(), + inputs: vec![NodeInput::Network], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), + }, + ), + ( + 1, DocumentNode { name: "Hue Shift Color".into(), inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::F32(50.))], @@ -169,21 +178,29 @@ impl Default for NodeGraphFrameLayer { }, ), ( - 1, + 2, DocumentNode { name: "Brighten Color".into(), - inputs: vec![NodeInput::Node(0), NodeInput::Value(value::TaggedValue::F32(10.))], + inputs: vec![NodeInput::Node(1), NodeInput::Value(value::TaggedValue::F32(10.))], implementation: DocumentNodeImplementation::Network(brighten_network), }, ), ( - 2, + 3, DocumentNode { name: "Map Image".into(), - inputs: vec![NodeInput::Network, NodeInput::Node(1)], + inputs: vec![NodeInput::Node(0), NodeInput::Node(2)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[])), }, ), + ( + 4, + DocumentNode { + name: "Output".into(), + inputs: vec![NodeInput::Node(3)], + implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), + }, + ), ] .into_iter() .collect(), diff --git a/node-graph/graph-craft/src/node_registry.rs b/node-graph/graph-craft/src/node_registry.rs index 5571a362bf..532b545d2e 100644 --- a/node-graph/graph-craft/src/node_registry.rs +++ b/node-graph/graph-craft/src/node_registry.rs @@ -21,17 +21,25 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ NodeIdentifier::new("graphene_core::ops::IdNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]), |proto_node, stack| { stack.push_fn(|nodes| { - let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap(); - let node = pre_node.then(graphene_core::ops::IdNode); - node.into_type_erased() + if let ProtoNodeInput::Node(pre_id) = proto_node.input { + let pre_node = nodes.get(pre_id as usize).unwrap(); + let node = pre_node.then(graphene_core::ops::IdNode); + node.into_type_erased() + } else { + graphene_core::ops::IdNode.into_type_erased() + } }) }, ), (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic]), |proto_node, stack| { stack.push_fn(|nodes| { - let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap(); - let node = pre_node.then(graphene_core::ops::IdNode); - node.into_type_erased() + if let ProtoNodeInput::Node(pre_id) = proto_node.input { + let pre_node = nodes.get(pre_id as usize).unwrap(); + let node = pre_node.then(graphene_core::ops::IdNode); + node.into_type_erased() + } else { + graphene_core::ops::IdNode.into_type_erased() + } }) }), ( From 155e82bec19871c094cbdfccb63468db26f29a85 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Mon, 14 Nov 2022 21:29:11 +0000 Subject: [PATCH 08/16] Add nodes that operate on images --- .../node_graph/node_graph_message_handler.rs | 11 +-- frontend/src/components/panels/NodeGraph.vue | 4 +- frontend/wasm/src/editor_api.rs | 7 +- graphene/src/layers/nodegraph_layer.rs | 26 ++--- node-graph/gcore/src/raster.rs | 16 ++-- node-graph/graph-craft/src/node_registry.rs | 79 +++++++++------- node-graph/gstd/src/raster.rs | 94 ++++++++++++++++++- 7 files changed, 162 insertions(+), 75 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 9f1510a205..1f2cba411d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -62,7 +62,7 @@ impl NodeGraphMessageHandler { let name = format!("Node {} Properties", document_node.name); let layout = match &document_node.implementation { DocumentNodeImplementation::Network(_) => match document_node.name.as_str() { - "Hue Shift Color" => vec![LayoutGroup::Row { + "Hue Shift Image" => vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Shift degrees".into(), @@ -95,7 +95,7 @@ impl NodeGraphMessageHandler { })), ], }], - "Brighten Color" => vec![LayoutGroup::Row { + "Brighten Image" => vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { value: "Brighten Amount".into(), @@ -292,10 +292,9 @@ impl MessageHandler -1 ? inputNodeConnectionIndexSearch : undefined; if (inputNodeConnectionIndex !== undefined) { - const oneBasedIndex = inputNodeConnectionIndex + 1; + // const oneBasedIndex = inputNodeConnectionIndex + 1; - this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), oneBasedIndex); + this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), 0); } } } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 28abf894ce..4005628b46 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -566,14 +566,13 @@ impl JsEditorHandle { let (ident, args) = match node_type.as_str() { "Identity" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1), - "Grayscale Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1), - "Brighten Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1), - "Hue Shift Color" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1), + "Grayscale Image" => (NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), 1), + "Brighten Image" => (NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), 2), + "Hue Shift Image" => (NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), 2), "Add" => ( NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("u32")), Type::Concrete(Cow::Borrowed("u32"))]), 2, ), - "Map Image" => (NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), 2), _ => panic!("Invalid node type: {}", node_type), }; diff --git a/graphene/src/layers/nodegraph_layer.rs b/graphene/src/layers/nodegraph_layer.rs index c34bbc6450..8eca078153 100644 --- a/graphene/src/layers/nodegraph_layer.rs +++ b/graphene/src/layers/nodegraph_layer.rs @@ -125,10 +125,10 @@ impl Default for NodeGraphFrameLayer { nodes: [( 0, DocumentNode { - name: "brighten".into(), + name: "Brighten Image Node".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new( - "graphene_core::raster::BrightenColorNode", + "graphene_std::raster::BrightenImageNode", &[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))], )), }, @@ -143,10 +143,10 @@ impl Default for NodeGraphFrameLayer { nodes: [( 0, DocumentNode { - name: "hue shift".into(), + name: "Hue Shift Image Node".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new( - "graphene_core::raster::HueShiftNode", + "graphene_std::raster::HueShiftImage", &[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))], )), }, @@ -159,7 +159,7 @@ impl Default for NodeGraphFrameLayer { mime: String::new(), network: NodeNetwork { inputs: vec![0], - output: 4, + output: 3, nodes: [ ( 0, @@ -172,32 +172,24 @@ impl Default for NodeGraphFrameLayer { ( 1, DocumentNode { - name: "Hue Shift Color".into(), - inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::F32(50.))], + name: "Hue Shift Image".into(), + inputs: vec![NodeInput::Node(0), NodeInput::Value(value::TaggedValue::F32(50.))], implementation: DocumentNodeImplementation::Network(hue_shift_network), }, ), ( 2, DocumentNode { - name: "Brighten Color".into(), + name: "Brighten Image".into(), inputs: vec![NodeInput::Node(1), NodeInput::Value(value::TaggedValue::F32(10.))], implementation: DocumentNodeImplementation::Network(brighten_network), }, ), ( 3, - DocumentNode { - name: "Map Image".into(), - inputs: vec![NodeInput::Node(0), NodeInput::Node(2)], - implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[])), - }, - ), - ( - 4, DocumentNode { name: "Output".into(), - inputs: vec![NodeInput::Node(3)], + inputs: vec![NodeInput::Node(2)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), }, ), diff --git a/node-graph/gcore/src/raster.rs b/node-graph/gcore/src/raster.rs index c3796a803f..d374249e6e 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/gcore/src/raster.rs @@ -5,16 +5,16 @@ use self::color::Color; pub mod color; #[derive(Debug, Clone, Copy)] -pub struct GrayscaleNode; +pub struct GrayscaleColorNode; -impl Node for GrayscaleNode { +impl Node for GrayscaleColorNode { type Output = Color; fn eval(self, color: Color) -> Color { let avg = (color.r() + color.g() + color.b()) / 3.0; Color::from_rgbaf32_unchecked(avg, avg, avg, color.a()) } } -impl<'n> Node for &'n GrayscaleNode { +impl<'n> Node for &'n GrayscaleColorNode { type Output = Color; fn eval(self, color: Color) -> Color { let avg = (color.r() + color.g() + color.b()) / 3.0; @@ -49,9 +49,9 @@ impl + Copy> BrightenColorNode { } #[derive(Debug, Clone, Copy)] -pub struct HueShiftNode>(N); +pub struct HueShiftColorNode>(N); -impl> Node for HueShiftNode { +impl> Node for HueShiftColorNode { type Output = Color; fn eval(self, color: Color) -> Color { let hue_shift = self.0.eval(()); @@ -59,7 +59,7 @@ impl> Node for HueShiftNode { Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha) } } -impl + Copy> Node for &HueShiftNode { +impl + Copy> Node for &HueShiftColorNode { type Output = Color; fn eval(self, color: Color) -> Color { let hue_shift = self.0.eval(()); @@ -68,7 +68,7 @@ impl + Copy> Node for &HueShiftNode { } } -impl + Copy> HueShiftNode { +impl + Copy> HueShiftColorNode { pub fn new(node: N) -> Self { Self(node) } @@ -105,7 +105,7 @@ mod test { #[test] fn map_node() { // let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()]; - (&GrayscaleNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.)); + (&GrayscaleColorNode).eval(Color::from_rgbf32_unchecked(1., 0., 0.)); /*let map = ForEachNode(MutWrapper(GrayscaleNode)); (&map).eval(array.iter_mut()); assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/ diff --git a/node-graph/graph-craft/src/node_registry.rs b/node-graph/graph-craft/src/node_registry.rs index 532b545d2e..66a1d7c9f4 100644 --- a/node-graph/graph-craft/src/node_registry.rs +++ b/node-graph/graph-craft/src/node_registry.rs @@ -198,9 +198,9 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ } }) }), - (NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]), |proto_node, stack| { + (NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]), |proto_node, stack| { stack.push_fn(|nodes| { - let node = DynAnyNode::new(graphene_core::raster::GrayscaleNode); + let node = DynAnyNode::new(graphene_core::raster::GrayscaleColorNode); if let ProtoNodeInput::Node(pre_id) = proto_node.input { let pre_node = nodes.get(pre_id as usize).unwrap(); @@ -230,14 +230,14 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ }, ), ( - NodeIdentifier::new("graphene_core::raster::HueShiftNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + NodeIdentifier::new("graphene_core::raster::HueShiftColorNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), |proto_node, stack| { info!("proto node {:?}", proto_node); stack.push_fn(|nodes| { let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Color Node constructed with out shift input node") }; let value_node = nodes.get(construction_nodes[0] as usize).unwrap(); let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node); - let node = DynAnyNode::new(graphene_core::raster::HueShiftNode::new(input_node)); + let node = DynAnyNode::new(graphene_core::raster::HueShiftColorNode::new(input_node)); if let ProtoNodeInput::Node(pre_id) = proto_node.input { let pre_node = nodes.get(pre_id as usize).unwrap(); @@ -267,45 +267,54 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[ unimplemented!() } }), - (NodeIdentifier::new("graph_craft::node_registry::GrayscaleImage", &[]), |proto_node, stack| { + (NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), |proto_node, stack| { stack.push_fn(move |nodes| { - let grayscale_node = DynAnyNode::new(FnNode::new(|mut image: Image| { - for pixel in &mut image.data { - let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.; - *pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a()); - } - image - })); + let node = DynAnyNode::new(graphene_std::raster::GrayscaleImageNode); if let ProtoNodeInput::Node(node_id) = proto_node.input { let pre_node = nodes.get(node_id as usize).unwrap(); - (pre_node).then(grayscale_node).into_type_erased() + (pre_node).then(node).into_type_erased() } else { - grayscale_node.into_type_erased() + node.into_type_erased() } }) }), - (NodeIdentifier::new("graph_craft::node_registry::HueShiftImage", &[]), |_proto_node, _stack| { - todo!(); - // stack.push_fn(move |nodes| { - - // let hue_shift_node = DynAnyNode::new(FnNode::new(|(mut image, amount): (Image, f32)| { - // for pixel in &mut image.data { - // let [mut hue, saturation, luminance, alpha] = pixel.to_hsla(); - // hue += amount; - // *pixel = Color::from_hsla(hue, saturation, luminance, alpha); - // } - // image - // })); - - // if let ProtoNodeInput::Node(node_id) = proto_node.input { - // let pre_node = nodes.get(node_id as usize).unwrap(); - // (pre_node).then(hue_shift_node).into_type_erased() - // } else { - // hue_shift_node.into_type_erased() - // } - // }) - }), + ( + NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + |proto_node, stack| { + stack.push_fn(move |nodes| { + let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Hue Shift Image Node constructed with out shift input node") }; + let value_node = nodes.get(construction_nodes[0] as usize).unwrap(); + let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node); + let node = DynAnyNode::new(graphene_std::raster::HueShiftImage::new(input_node)); + + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(node).into_type_erased() + } else { + node.into_type_erased() + } + }) + }, + ), + ( + NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + |proto_node, stack| { + stack.push_fn(move |nodes| { + let ConstructionArgs::Nodes(construction_nodes) = proto_node.construction_args else { unreachable!("Brighten Image Node constructed with out brighten input node") }; + let value_node = nodes.get(construction_nodes[0] as usize).unwrap(); + let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node); + let node = DynAnyNode::new(graphene_std::raster::BrightenImageNode::new(input_node)); + + if let ProtoNodeInput::Node(node_id) = proto_node.input { + let pre_node = nodes.get(node_id as usize).unwrap(); + (pre_node).then(node).into_type_erased() + } else { + node.into_type_erased() + } + }) + }, + ), ( NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]), |_proto_node, stack| { diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 573553512f..cdae7cf1d9 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -165,16 +165,104 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<() }) } +#[derive(Debug, Clone, Copy)] +pub struct GrayscaleImageNode; + +impl Node for GrayscaleImageNode { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + for pixel in &mut image.data { + let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.; + *pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a()); + } + image + } +} +impl Node for &GrayscaleImageNode { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + for pixel in &mut image.data { + let avg = (pixel.r() + pixel.g() + pixel.b()) / 3.; + *pixel = Color::from_rgbaf32_unchecked(avg, avg, avg, pixel.a()); + } + image + } +} + +#[derive(Debug, Clone, Copy)] +pub struct BrightenImageNode>(N); + +impl> Node for BrightenImageNode { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + let brightness = self.0.eval(()); + let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.); + for pixel in &mut image.data { + *pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a()); + } + image + } +} +impl + Copy> Node for &BrightenImageNode { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + let brightness = self.0.eval(()); + let per_channel = |col: f32| (col + brightness / 255.).clamp(0., 1.); + for pixel in &mut image.data { + *pixel = Color::from_rgbaf32_unchecked(per_channel(pixel.r()), per_channel(pixel.g()), per_channel(pixel.b()), pixel.a()); + } + image + } +} + +impl + Copy> BrightenImageNode { + pub fn new(node: N) -> Self { + Self(node) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct HueShiftImage>(N); + +impl> Node for HueShiftImage { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + let hue_shift = self.0.eval(()); + for pixel in &mut image.data { + let [hue, saturation, luminance, alpha] = pixel.to_hsla(); + *pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha); + } + image + } +} +impl + Copy> Node for &HueShiftImage { + type Output = Image; + fn eval(self, mut image: Image) -> Image { + let hue_shift = self.0.eval(()); + for pixel in &mut image.data { + let [hue, saturation, luminance, alpha] = pixel.to_hsla(); + *pixel = Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha); + } + image + } +} + +impl + Copy> HueShiftImage { + pub fn new(node: N) -> Self { + Self(node) + } +} + #[cfg(test)] mod test { use super::*; use graphene_core::raster::color::Color; - use graphene_core::raster::GrayscaleNode; + use graphene_core::raster::GrayscaleColorNode; #[test] fn map_node() { let array = [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()]; - let map = MapNode(GrayscaleNode, PhantomData); + let map = MapNode(GrayscaleColorNode, PhantomData); let values = map.eval(array.into_iter()); assert_eq!(values[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap()); } @@ -182,7 +270,7 @@ mod test { #[test] fn load_image() { let image = image_node::<&str>(); - let gray = MapImageNode::new(GrayscaleNode); + let gray = MapImageNode::new(GrayscaleColorNode); let grayscale_picture = image.then(MapResultNode::new(&gray)); let export = export_image_node(); From d69c145956a1d70291be1160ba9d0e5eef10734b Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 14 Nov 2022 21:28:42 -0800 Subject: [PATCH 09/16] Fixups --- .../node_graph/node_graph_message_handler.rs | 10 +++++----- .../document/properties_panel/utility_functions.rs | 5 ++++- .../components/widgets/groups/WidgetSection.vue | 4 ++-- node-graph/graph-craft/src/document.rs | 14 +++++++------- node-graph/graph-craft/src/lib.rs | 4 ++-- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 1f2cba411d..d8a5e0c20b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -59,13 +59,13 @@ impl NodeGraphMessageHandler { let Some(document_node) = network.nodes.get(node_id) else { continue; }; - let name = format!("Node {} Properties", document_node.name); + let name = document_node.name.clone(); let layout = match &document_node.implementation { DocumentNodeImplementation::Network(_) => match document_node.name.as_str() { "Hue Shift Image" => vec![LayoutGroup::Row { widgets: vec![ WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Shift degrees".into(), + value: "Shift Degrees".into(), ..Default::default() })), WidgetHolder::new(Widget::Separator(Separator { @@ -129,7 +129,7 @@ impl NodeGraphMessageHandler { }], _ => vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("Cannot currently display properties for network {}", document_node.name), + value: format!("Cannot currently display parameters for network {}", document_node.name), ..Default::default() }))], }], @@ -137,7 +137,7 @@ impl NodeGraphMessageHandler { DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() { "graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("{} requires no properties", document_node.name), + value: format!("{} exposes no parameters", document_node.name), ..Default::default() }))], }], @@ -145,7 +145,7 @@ impl NodeGraphMessageHandler { vec![ LayoutGroup::Row { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("TODO: {} properties", unknown), + value: format!("TODO: {} parameters", unknown), ..Default::default() }))], }, diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs index 31f40662aa..25d449f5f3 100644 --- a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs @@ -269,7 +269,10 @@ pub fn register_artwork_layer_properties( direction: SeparatorDirection::Horizontal, })), WidgetHolder::new(Widget::TextLabel(TextLabel { - value: LayerDataTypeDiscriminant::from(&layer.data).to_string(), + value: match &layer.data { + LayerDataType::NodeGraphFrame(_) => "Node Graph Frame".into(), + other => LayerDataTypeDiscriminant::from(other).to_string(), + }, ..TextLabel::default() })), WidgetHolder::new(Widget::Separator(Separator { diff --git a/frontend/src/components/widgets/groups/WidgetSection.vue b/frontend/src/components/widgets/groups/WidgetSection.vue index d74b9ecab7..69453a0f96 100644 --- a/frontend/src/components/widgets/groups/WidgetSection.vue +++ b/frontend/src/components/widgets/groups/WidgetSection.vue @@ -88,11 +88,11 @@ .widget-row { &:first-child { - margin-top: -1px; + margin-top: calc(4px - 1px); } &:last-child { - margin-bottom: -1px; + margin-bottom: calc(4px - 1px); } > .text-label:first-of-type { diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 60a41565b2..37a5cea83b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -235,7 +235,7 @@ mod test { ( 0, DocumentNode { - name: "cons".into(), + name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), }, @@ -243,7 +243,7 @@ mod test { ( 1, DocumentNode { - name: "add".into(), + name: "Add".into(), inputs: vec![NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), }, @@ -265,7 +265,7 @@ mod test { ( 1, DocumentNode { - name: "cons".into(), + name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), }, @@ -273,7 +273,7 @@ mod test { ( 2, DocumentNode { - name: "add".into(), + name: "Add".into(), inputs: vec![NodeInput::Node(1)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), }, @@ -312,7 +312,7 @@ mod test { #[test] fn resolve_proto_node_add() { let document_node = DocumentNode { - name: "cons".into(), + name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), }; @@ -385,7 +385,7 @@ mod test { ( 10, DocumentNode { - name: "cons".into(), + name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Node(14)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), }, @@ -401,7 +401,7 @@ mod test { ( 11, DocumentNode { - name: "add".into(), + name: "Add".into(), inputs: vec![NodeInput::Node(10)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), }, diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index fc9d6da4b1..151939e28b 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -68,7 +68,7 @@ mod tests { ( 0, DocumentNode { - name: "cons".into(), + name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new( "graphene_core::structural::ConsNode", @@ -79,7 +79,7 @@ mod tests { ( 1, DocumentNode { - name: "add".into(), + name: "Add".into(), inputs: vec![NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new( "graphene_core::ops::AddNode", From a5e28a6f6a75f010c978fecc40940e4e91012b16 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 14 Nov 2022 22:12:00 -0800 Subject: [PATCH 10/16] Show node parameters together with layer properties --- .../document/properties_panel/utility_functions.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs index 25d449f5f3..1fea4e16d7 100644 --- a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs @@ -325,14 +325,16 @@ pub fn register_artwork_layer_properties( LayerDataType::NodeGraphFrame(node_graph_frame) => { let is_graph_open = node_graph_message_handler.layer_path.as_ref().filter(|node_graph| *node_graph == &layer_path).is_some(); let selected_nodes = &node_graph_message_handler.selected_nodes; + + let mut properties_sections = vec![ + node_section_transform(layer, persistent_data), + node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open), + ]; if !selected_nodes.is_empty() && is_graph_open { - node_graph_message_handler.collate_properties(&node_graph_frame) - } else { - vec![ - node_section_transform(layer, persistent_data), - node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open), - ] + let parameters_sections = node_graph_message_handler.collate_properties(node_graph_frame); + properties_sections.extend(parameters_sections.into_iter()); } + properties_sections } LayerDataType::Folder(_) => { vec![node_section_transform(layer, persistent_data)] From 9c390eade423ce3bfcdaa9030b767f97944d9952 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Thu, 17 Nov 2022 18:31:38 +0000 Subject: [PATCH 11/16] New nodes don't crash editor --- Cargo.lock | 1 + .../document/node_graph/node_graph_message.rs | 6 +- .../node_graph/node_graph_message_handler.rs | 210 +++++------------- .../document_node_types.rs | 93 ++++++++ .../node_properties.rs | 111 +++++++++ frontend/src/components/panels/NodeGraph.vue | 6 +- frontend/wasm/src/editor_api.rs | 19 +- graphene/src/layers/nodegraph_layer.rs | 16 +- node-graph/graph-craft/Cargo.toml | 7 +- node-graph/graph-craft/src/document.rs | 34 ++- node-graph/graph-craft/src/document/value.rs | 3 +- node-graph/graph-craft/src/lib.rs | 8 +- node-graph/gstd/Cargo.toml | 5 + node-graph/gstd/src/raster.rs | 3 +- 14 files changed, 320 insertions(+), 202 deletions(-) create mode 100644 editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs create mode 100644 editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs diff --git a/Cargo.lock b/Cargo.lock index 6f4dfee959..e67bbc9990 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -399,6 +399,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", + "serde", "syn", ] diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 6465d02da6..4f3fa91e42 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -1,6 +1,5 @@ use crate::messages::prelude::*; use graph_craft::document::{value::TaggedValue, NodeId}; -use graph_craft::proto::NodeIdentifier; #[remain::sorted] #[impl_message(Message, DocumentMessage, NodeGraph)] @@ -17,10 +16,7 @@ pub enum NodeGraphMessage { // Having the caller generate the id means that we don't have to return it. This can be a random u64. node_id: NodeId, // I don't really know what this is for (perhaps a user identifiable name). - name: String, - // The node identifier must mach that found in `node-graph/graph-craft/src/node_registry.rs` e.g. "graphene_core::raster::GrayscaleNode - identifier: NodeIdentifier, - num_inputs: u32, + node_type: String, }, DeleteNode { node_id: NodeId, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index d8a5e0c20b..1c22f18060 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,13 +1,13 @@ -use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder}; -use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode}; -use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel}; +use crate::messages::layout::utility_types::layout_widget::LayoutGroup; use crate::messages::prelude::*; -use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; use graphene::document::Document; use graphene::layers::layer_info::LayerDataType; use graphene::layers::nodegraph_layer::NodeGraphFrameLayer; +mod document_node_types; +mod node_properties; + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct FrontendNode { pub id: graph_craft::document::NodeId, @@ -55,111 +55,11 @@ impl NodeGraphMessageHandler { let network = &node_graph_frame.network; let mut section = Vec::new(); for node_id in &self.selected_nodes { - let node = *node_id; let Some(document_node) = network.nodes.get(node_id) else { continue; }; - let name = document_node.name.clone(); - let layout = match &document_node.implementation { - DocumentNodeImplementation::Network(_) => match document_node.name.as_str() { - "Hue Shift Image" => vec![LayoutGroup::Row { - widgets: vec![ - WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Shift Degrees".into(), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), - WidgetHolder::new(Widget::NumberInput(NumberInput { - value: Some({ - let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else { - panic!("Hue rotate should be f32") - }; - x as f64 - }), - unit: "°".into(), - mode: NumberInputMode::Range, - range_min: Some(-180.), - range_max: Some(180.), - on_update: WidgetCallback::new(move |number_input: &NumberInput| { - NodeGraphMessage::SetInputValue { - node, - input_index: 1, - value: TaggedValue::F32(number_input.value.unwrap() as f32), - } - .into() - }), - ..NumberInput::default() - })), - ], - }], - "Brighten Image" => vec![LayoutGroup::Row { - widgets: vec![ - WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Brighten Amount".into(), - ..Default::default() - })), - WidgetHolder::new(Widget::Separator(Separator { - separator_type: SeparatorType::Unrelated, - direction: SeparatorDirection::Horizontal, - })), - WidgetHolder::new(Widget::NumberInput(NumberInput { - value: Some({ - let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else { - panic!("Brighten amount should be f32") - }; - x as f64 - }), - mode: NumberInputMode::Range, - range_min: Some(-255.), - range_max: Some(255.), - on_update: WidgetCallback::new(move |number_input: &NumberInput| { - NodeGraphMessage::SetInputValue { - node, - input_index: 1, - value: TaggedValue::F32(number_input.value.unwrap() as f32), - } - .into() - }), - ..NumberInput::default() - })), - ], - }], - _ => vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("Cannot currently display parameters for network {}", document_node.name), - ..Default::default() - }))], - }], - }, - DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() { - "graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("{} exposes no parameters", document_node.name), - ..Default::default() - }))], - }], - unknown => { - vec![ - LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: format!("TODO: {} parameters", unknown), - ..Default::default() - }))], - }, - LayoutGroup::Row { - widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { - value: "Add in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs".to_string(), - ..Default::default() - }))], - }, - ] - } - }, - }; - section.push(LayoutGroup::Section { name, layout }); + + section.push(node_properties::generate_node_properties(document_node, *node_id)); } section @@ -173,7 +73,7 @@ impl NodeGraphMessageHandler { let links = network .nodes .iter() - .flat_map(|(link_end, node)| node.inputs.iter().enumerate().map(move |(index, input)| (input, link_end, index))) + .flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index))) .filter_map(|(input, &link_end, link_end_input_index)| { if let NodeInput::Node(link_start) = *input { Some(FrontendNodeLink { @@ -226,54 +126,59 @@ impl MessageHandler= input_node.inputs.len() { - input_node.inputs.extend(((input_node.inputs.len() - 1)..input_node_connector_index).map(|_| NodeInput::Network)); - } - input_node.inputs[input_node_connector_index] = NodeInput::Node(output_node); + }; + let Some((actual_index, _)) = input_node.inputs.iter().enumerate().filter(|input|input.1.is_exposed()).nth(input_node_connector_index) else { + error!("Failed to find actual index of connector indes {input_node_connector_index} on node {input_node:#?}"); + return; + }; + input_node.inputs[actual_index] = NodeInput::Node(output_node); info!("Inputs: {:?}", input_node.inputs); Self::send_graph(network, responses); } - NodeGraphMessage::CreateNode { - node_id, - name, - identifier, - num_inputs, - } => { - if let Some(network) = self.get_active_network_mut(document) { - let inner_network = NodeNetwork { - inputs: (0..num_inputs).map(|_| 0).collect(), - output: 0, - nodes: [( - node_id, - DocumentNode { - name: format!("{}_impl", name), - // TODO: Allow inserting nodes that contain other nodes. - implementation: DocumentNodeImplementation::Unresolved(identifier), - inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(), - }, - )] - .into_iter() - .collect(), - }; - network.nodes.insert( - node_id, + NodeGraphMessage::CreateNode { node_id, node_type } => { + let Some(network) = self.get_active_network_mut(document) else{ + warn!("No network"); + return; + }; + + let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else{ + responses.push_back(DialogMessage::DisplayDialogError { title: "Cannot insert node".to_string(), description: format!("The document node '{node_type}' does not exist in the document node list") }.into()); + return; + }; + + let num_inputs = document_node_type.default_inputs.len(); + + let inner_network = NodeNetwork { + inputs: (0..num_inputs).map(|_| 0).collect(), + output: 0, + nodes: [( + 0, DocumentNode { - name, - inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(), + name: format!("{}_impl", document_node_type.name), // TODO: Allow inserting nodes that contain other nodes. - implementation: DocumentNodeImplementation::Network(inner_network), + implementation: DocumentNodeImplementation::Unresolved(document_node_type.identifier.clone()), + inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(), }, - ); - Self::send_graph(network, responses); - } + )] + .into_iter() + .collect(), + }; + network.nodes.insert( + node_id, + DocumentNode { + name: node_type.clone(), + inputs: document_node_type.default_inputs.to_vec(), + // TODO: Allow inserting nodes that contain other nodes. + implementation: DocumentNodeImplementation::Network(inner_network), + }, + ); + Self::send_graph(network, responses); } NodeGraphMessage::DeleteNode { node_id } => { if let Some(network) = self.get_active_network_mut(document) { network.nodes.remove(&node_id); - // TODO: Update UI if it is not already updated. + Self::send_graph(network, responses); } } NodeGraphMessage::OpenNodeGraph { layer_path } => { @@ -287,19 +192,8 @@ impl MessageHandler { @@ -313,7 +207,7 @@ impl MessageHandler= node.inputs.len() { node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network)); } - node.inputs[input_index] = NodeInput::Value(value); + node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false }; } } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs new file mode 100644 index 0000000000..02e7b93394 --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -0,0 +1,93 @@ +use std::borrow::Cow; + +use graph_craft::document::value::TaggedValue; +use graph_craft::document::NodeInput; +use graph_craft::proto::{NodeIdentifier, Type}; +use graphene_std::raster::Image; + +use super::FrontendNodeType; + +pub struct DocumentNodeType { + pub name: &'static str, + pub identifier: NodeIdentifier, + pub default_inputs: &'static [NodeInput], +} + +// TODO: Dynamic node library +static DOCUMENT_NODE_TYPES: [DocumentNodeType; 5] = [ + DocumentNodeType { + name: "Identity", + identifier: NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), + default_inputs: &[NodeInput::Node(0)], + }, + DocumentNodeType { + name: "Grayscale Image", + identifier: NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), + default_inputs: &[NodeInput::Value { + tagged_value: TaggedValue::Image(Image { + width: 0, + height: 0, + data: Vec::new(), + }), + exposed: true, + }], + }, + DocumentNodeType { + name: "Brighten Image", + identifier: NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + default_inputs: &[ + NodeInput::Value { + tagged_value: TaggedValue::Image(Image { + width: 0, + height: 0, + data: Vec::new(), + }), + exposed: true, + }, + NodeInput::Value { + tagged_value: TaggedValue::F32(10.), + exposed: false, + }, + ], + }, + DocumentNodeType { + name: "Hue Shift Image", + identifier: NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), + default_inputs: &[ + NodeInput::Value { + tagged_value: TaggedValue::Image(Image { + width: 0, + height: 0, + data: Vec::new(), + }), + exposed: true, + }, + NodeInput::Value { + tagged_value: TaggedValue::F32(50.), + exposed: false, + }, + ], + }, + DocumentNodeType { + name: "Add", + identifier: NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("u32")), Type::Concrete(Cow::Borrowed("u32"))]), + default_inputs: &[ + NodeInput::Value { + tagged_value: TaggedValue::U32(0), + exposed: false, + }, + NodeInput::Value { + tagged_value: TaggedValue::U32(0), + exposed: false, + }, + ], + }, +]; + +pub fn resolve_document_node_type(name: &str) -> Option<&DocumentNodeType> { + DOCUMENT_NODE_TYPES.iter().find(|node| node.name == name) +} + +pub fn collect_node_types() -> Vec { + DOCUMENT_NODE_TYPES.iter().map(|node_type| FrontendNodeType { name: node_type.name.to_string() }).collect() +} diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs new file mode 100644 index 0000000000..e4fc2971b2 --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -0,0 +1,111 @@ +use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder}; +use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode}; +use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel}; +use crate::messages::prelude::NodeGraphMessage; + +use graph_craft::document::value::TaggedValue; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; + +pub fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId) -> LayoutGroup { + let name = document_node.name.clone(); + let layout = match &document_node.implementation { + DocumentNodeImplementation::Network(_) => match document_node.name.as_str() { + "Hue Shift Image" => vec![LayoutGroup::Row { + widgets: vec![ + WidgetHolder::new(Widget::TextLabel(TextLabel { + value: "Shift Degrees".into(), + ..Default::default() + })), + WidgetHolder::new(Widget::Separator(Separator { + separator_type: SeparatorType::Unrelated, + direction: SeparatorDirection::Horizontal, + })), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some({ + let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else { + panic!("Hue rotate should be f32") + }; + x as f64 + }), + unit: "°".into(), + mode: NumberInputMode::Range, + range_min: Some(-180.), + range_max: Some(180.), + on_update: WidgetCallback::new(move |number_input: &NumberInput| { + NodeGraphMessage::SetInputValue { + node: node_id, + input_index: 1, + value: TaggedValue::F32(number_input.value.unwrap() as f32), + } + .into() + }), + ..NumberInput::default() + })), + ], + }], + "Brighten Image" => vec![LayoutGroup::Row { + widgets: vec![ + WidgetHolder::new(Widget::TextLabel(TextLabel { + value: "Brighten Amount".into(), + ..Default::default() + })), + WidgetHolder::new(Widget::Separator(Separator { + separator_type: SeparatorType::Unrelated, + direction: SeparatorDirection::Horizontal, + })), + WidgetHolder::new(Widget::NumberInput(NumberInput { + value: Some({ + let NodeInput::Value {tagged_value: TaggedValue::F32(x), ..} = document_node.inputs[1] else { + panic!("Brighten amount should be f32") + }; + x as f64 + }), + mode: NumberInputMode::Range, + range_min: Some(-255.), + range_max: Some(255.), + on_update: WidgetCallback::new(move |number_input: &NumberInput| { + NodeGraphMessage::SetInputValue { + node: node_id, + input_index: 1, + value: TaggedValue::F32(number_input.value.unwrap() as f32), + } + .into() + }), + ..NumberInput::default() + })), + ], + }], + _ => vec![LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { + value: format!("Cannot currently display parameters for network {}", document_node.name), + ..Default::default() + }))], + }], + }, + DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() { + "graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { + value: format!("{} exposes no parameters", document_node.name), + ..Default::default() + }))], + }], + unknown => { + vec![ + LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { + value: format!("TODO: {} parameters", unknown), + ..Default::default() + }))], + }, + LayoutGroup::Row { + widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { + value: "Add in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs".to_string(), + ..Default::default() + }))], + }, + ] + } + }, + }; + LayoutGroup::Section { name, layout } +} diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index a0338c29bd..aa15085327 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -35,8 +35,8 @@ class="node" :class="{ selected: selected.includes(node.id) }" :style="{ - '--offset-left': 8 + Number(node.id < 9n ? node.id : node.id - 9n) * 7, - '--offset-top': 4 + Number(node.id < 9n ? node.id : node.id - 9n) * 2, + '--offset-left': 8 + Number(node.id < 9n ? node.id : node.id - 7n) * 7, + '--offset-top': 4 + Number(node.id < 9n ? node.id : node.id - 7n) * 2, '--data-color': 'var(--color-data-raster)', '--data-color-dim': 'var(--color-data-raster-dim)', }" @@ -491,7 +491,7 @@ export default defineComponent({ if (inputNodeConnectionIndex !== undefined) { // const oneBasedIndex = inputNodeConnectionIndex + 1; - this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), 0); + this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex); } } } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 4005628b46..cc480acd9e 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -553,9 +553,6 @@ impl JsEditorHandle { /// Creates a new document node in the node graph #[wasm_bindgen(js_name = createNode)] pub fn create_node(&self, node_type: String) { - use graph_craft::proto::{NodeIdentifier, Type}; - use std::borrow::Cow; - fn generate_node_id() -> u64 { static mut NODE_ID: u64 = 10; unsafe { @@ -564,23 +561,9 @@ impl JsEditorHandle { } } - let (ident, args) = match node_type.as_str() { - "Identity" => (NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Concrete(Cow::Borrowed("Any<'_>"))]), 1), - "Grayscale Image" => (NodeIdentifier::new("graphene_std::raster::GrayscaleImageNode", &[]), 1), - "Brighten Image" => (NodeIdentifier::new("graphene_std::raster::BrightenImageNode", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), 2), - "Hue Shift Image" => (NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]), 2), - "Add" => ( - NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Concrete(Cow::Borrowed("u32")), Type::Concrete(Cow::Borrowed("u32"))]), - 2, - ), - _ => panic!("Invalid node type: {}", node_type), - }; - let message = NodeGraphMessage::CreateNode { node_id: generate_node_id(), - name: node_type, - identifier: ident, - num_inputs: args, + node_type, }; self.dispatch(message); } diff --git a/graphene/src/layers/nodegraph_layer.rs b/graphene/src/layers/nodegraph_layer.rs index 8eca078153..f8301dbe38 100644 --- a/graphene/src/layers/nodegraph_layer.rs +++ b/graphene/src/layers/nodegraph_layer.rs @@ -173,7 +173,13 @@ impl Default for NodeGraphFrameLayer { 1, DocumentNode { name: "Hue Shift Image".into(), - inputs: vec![NodeInput::Node(0), NodeInput::Value(value::TaggedValue::F32(50.))], + inputs: vec![ + NodeInput::Node(0), + NodeInput::Value { + tagged_value: value::TaggedValue::F32(50.), + exposed: false, + }, + ], implementation: DocumentNodeImplementation::Network(hue_shift_network), }, ), @@ -181,7 +187,13 @@ impl Default for NodeGraphFrameLayer { 2, DocumentNode { name: "Brighten Image".into(), - inputs: vec![NodeInput::Node(1), NodeInput::Value(value::TaggedValue::F32(10.))], + inputs: vec![ + NodeInput::Node(1), + NodeInput::Value { + tagged_value: value::TaggedValue::F32(10.), + exposed: false, + }, + ], implementation: DocumentNodeImplementation::Network(brighten_network), }, ), diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index e1a789cbb2..771edaadba 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -15,8 +15,7 @@ borrow_stack = { path = "../borrow_stack" } dyn-clone = "1.0" rand_chacha = "0.3.1" log = "0.4" +serde = { version = "1", features = ["derive"], optional = true } -[dependencies.serde] -version = "1.0" -optional = true -features = ["derive"] +[features] +serde = ["dep:serde", "graphene-std/serde"] diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 37a5cea83b..7c2d7a087b 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -54,7 +54,7 @@ impl DocumentNode { let first = self.inputs.remove(0); if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation { let (input, mut args) = match first { - NodeInput::Value(tagged_value) => { + NodeInput::Value { tagged_value, .. } => { assert_eq!(self.inputs.len(), 0); (ProtoNodeInput::None, ConstructionArgs::Value(tagged_value.to_value())) } @@ -63,7 +63,7 @@ impl DocumentNode { }; assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter"); assert!( - !self.inputs.iter().any(|input| matches!(input, NodeInput::Value(_))), + !self.inputs.iter().any(|input| matches!(input, NodeInput::Value { .. })), "recieved value as parameter. inupts: {:#?}, construction_args: {:#?}", &self.inputs, &args @@ -90,7 +90,7 @@ impl DocumentNode { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum NodeInput { Node(NodeId), - Value(value::TaggedValue), + Value { tagged_value: value::TaggedValue, exposed: bool }, Network, } @@ -100,13 +100,20 @@ impl NodeInput { *self = NodeInput::Node(f(*id)) } } + pub fn is_exposed(&self) -> bool { + if let NodeInput::Value { exposed, .. } = self { + *exposed + } else { + true + } + } } impl PartialEq for NodeInput { fn eq(&self, other: &Self) -> bool { match (&self, &other) { (Self::Node(n1), Self::Node(n2)) => n1 == n2, - (Self::Value(v1), Self::Value(v2)) => v1 == v2, + (Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2, _ => core::mem::discriminant(self) == core::mem::discriminant(other), } } @@ -169,12 +176,12 @@ impl NodeNetwork { let network_input = self.nodes.get_mut(network_input).unwrap(); network_input.populate_first_network_input(node, *offset); } - NodeInput::Value(value) => { - let name = format!("Value: {:?}", value.clone().to_value()); + NodeInput::Value { tagged_value, exposed } => { + let name = format!("Value: {:?}", tagged_value.clone().to_value()); let new_id = map_ids(id, gen_id()); let value_node = DocumentNode { name: name.clone(), - inputs: vec![NodeInput::Value(value)], + inputs: vec![NodeInput::Value { tagged_value, exposed }], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])), }; assert!(!self.nodes.contains_key(&new_id)); @@ -294,7 +301,13 @@ mod test { 1, DocumentNode { name: "Inc".into(), - inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(2))], + inputs: vec![ + NodeInput::Network, + NodeInput::Value { + tagged_value: value::TaggedValue::U32(2), + exposed: false, + }, + ], implementation: DocumentNodeImplementation::Network(add_network()), }, )] @@ -394,7 +407,10 @@ mod test { 14, DocumentNode { name: "Value: 2".into(), - inputs: vec![NodeInput::Value(value::TaggedValue::U32(2))], + inputs: vec![NodeInput::Value { + tagged_value: value::TaggedValue::U32(2), + exposed: false, + }], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])), }, ), diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 6ce5d52c3e..17a1bb1974 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -11,7 +11,7 @@ pub enum TaggedValue { String(String), U32(u32), F32(f32), - //Image(graphene_std::raster::Image), + Image(graphene_std::raster::Image), Color(graphene_core::raster::color::Color), } @@ -21,6 +21,7 @@ impl TaggedValue { TaggedValue::String(x) => Box::new(x), TaggedValue::U32(x) => Box::new(x), TaggedValue::F32(x) => Box::new(x), + TaggedValue::Image(x) => Box::new(x), TaggedValue::Color(x) => Box::new(x), } } diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index 151939e28b..acbfa27e0b 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -100,7 +100,13 @@ mod tests { 0, DocumentNode { name: "Inc".into(), - inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::U32(1))], + inputs: vec![ + NodeInput::Network, + NodeInput::Value { + tagged_value: value::TaggedValue::U32(1), + exposed: false, + }, + ], implementation: DocumentNodeImplementation::Network(add_network()), }, )] diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 0b0812db5f..6c7518afc5 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -26,3 +26,8 @@ proc-macro2 = {version = "1.0", default-features = false, features = ["proc-macr quote = {version = "1.0", default-features = false } image = "*" dyn-clone = "1.0" + +[dependencies.serde] +version = "1.0" +optional = true +features = ["derive"] diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index cdae7cf1d9..11c839d816 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -98,7 +98,8 @@ impl Node for BufferNode { } } -#[derive(Clone, DynAny)] +#[derive(Clone, Debug, PartialEq, DynAny)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Image { pub width: u32, pub height: u32, From 9dd53a78edc384a668869fc6637bfe67388e0453 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Thu, 17 Nov 2022 19:32:50 +0000 Subject: [PATCH 12/16] Fix tests --- node-graph/graph-craft/src/node_registry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/node-graph/graph-craft/src/node_registry.rs b/node-graph/graph-craft/src/node_registry.rs index 66a1d7c9f4..c947cafc29 100644 --- a/node-graph/graph-craft/src/node_registry.rs +++ b/node-graph/graph-craft/src/node_registry.rs @@ -419,7 +419,7 @@ mod protograph_testing { let grayscale_protonode = ProtoNode { construction_args: ConstructionArgs::Nodes(vec![]), input: ProtoNodeInput::Node(0), - identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]), + identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]), }; push_node(grayscale_protonode, &stack); @@ -456,7 +456,7 @@ mod protograph_testing { let grayscale_protonode = ProtoNode { construction_args: ConstructionArgs::Nodes(vec![]), input: ProtoNodeInput::None, - identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]), + identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]), }; push_node(grayscale_protonode, &stack); From 77ad6a3c72872240726c7f803c846385029a6608 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Thu, 17 Nov 2022 20:44:52 +0000 Subject: [PATCH 13/16] Node positions backend --- .../document/node_graph/node_graph_message.rs | 4 +++ .../node_graph/node_graph_message_handler.rs | 32 ++++++++++++++++++- frontend/src/components/panels/NodeGraph.vue | 4 +-- frontend/src/wasm-communication/messages.ts | 19 ++++++++--- frontend/wasm/src/editor_api.rs | 7 ++++ graphene/src/layers/nodegraph_layer.rs | 6 ++++ node-graph/graph-craft/src/document.rs | 18 +++++++++++ 7 files changed, 82 insertions(+), 8 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 4f3fa91e42..6d9aa2008d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -21,6 +21,10 @@ pub enum NodeGraphMessage { DeleteNode { node_id: NodeId, }, + MoveSelectedNodes { + displacement_x: i32, + displacement_y: i32, + }, OpenNodeGraph { layer_path: Vec, }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 1c22f18060..b2e2f373ab 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,6 +1,6 @@ use crate::messages::layout::utility_types::layout_widget::LayoutGroup; use crate::messages::prelude::*; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork}; use graphene::document::Document; use graphene::layers::layer_info::LayerDataType; use graphene::layers::nodegraph_layer::NodeGraphFrameLayer; @@ -8,11 +8,20 @@ use graphene::layers::nodegraph_layer::NodeGraphFrameLayer; mod document_node_types; mod node_properties; +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum DataType { + Raster, +} + #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct FrontendNode { pub id: graph_craft::document::NodeId, #[serde(rename = "displayName")] pub display_name: String, + #[serde(rename = "exposedInputs")] + pub exposed_inputs: Vec, + pub outputs: Vec, + pub position: (i32, i32), } // (link_start, link_end, link_end_input_index) @@ -92,6 +101,9 @@ impl NodeGraphMessageHandler { nodes.push(FrontendNode { id: *id, display_name: node.name.clone(), + exposed_inputs: node.inputs.iter().filter(|input| input.is_exposed()).map(|_| DataType::Raster).collect(), + outputs: vec![DataType::Raster], + position: node.metadata.position, }) } log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links); @@ -159,6 +171,7 @@ impl MessageHandler { + let Some(network) = self.get_active_network_mut(document) else{ + warn!("No network"); + return; + }; + + for node_id in &self.selected_nodes { + if let Some(node) = network.nodes.get_mut(node_id) { + node.metadata.position.0 += displacement_x; + node.metadata.position.1 += displacement_y; + } + } + } NodeGraphMessage::OpenNodeGraph { layer_path } => { if let Some(_old_layer_path) = self.layer_path.replace(layer_path) { // TODO: Necessary cleanup of old node graph diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index aa15085327..f521aeef86 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -35,8 +35,8 @@ class="node" :class="{ selected: selected.includes(node.id) }" :style="{ - '--offset-left': 8 + Number(node.id < 9n ? node.id : node.id - 7n) * 7, - '--offset-top': 4 + Number(node.id < 9n ? node.id : node.id - 7n) * 2, + '--offset-left': node.position?.x || 0, + '--offset-top': node.position?.y || 0, '--data-color': 'var(--color-data-raster)', '--data-color-dim': 'var(--color-data-raster-dim)', }" diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 1ccc171f18..93a3f7c454 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -12,6 +12,11 @@ export class JsMessage { static readonly jsMessageMarker = true; } +const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] })); +const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) })); + +export type XY = { x: number; y: number }; + // ============================================================================ // Add additional classes below to replicate Rust's `FrontendMessage`s and data structures. // @@ -64,10 +69,19 @@ export class FrontendDocumentDetails extends DocumentDetails { readonly id!: bigint; } +export type DataType = "Raster" | "Color" | "Image" | "F32"; + export class FrontendNode { readonly id!: bigint; readonly displayName!: string; + + readonly exposedInputs!: DataType[]; + + readonly outputs!: DataType[]; + + @TupleToVec2 + readonly position!: XY | undefined; } export class FrontendNodeLink { @@ -403,11 +417,6 @@ export class UpdateDocumentArtboards extends JsMessage { readonly svg!: string; } -const TupleToVec2 = Transform(({ value }: { value: [number, number] | undefined }) => (value === undefined ? undefined : { x: value[0], y: value[1] })); -const BigIntTupleToVec2 = Transform(({ value }: { value: [bigint, bigint] | undefined }) => (value === undefined ? undefined : { x: Number(value[0]), y: Number(value[1]) })); - -export type XY = { x: number; y: number }; - export class UpdateDocumentScrollbars extends JsMessage { @TupleToVec2 readonly position!: XY; diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index cc480acd9e..f105f91fa8 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -575,6 +575,13 @@ impl JsEditorHandle { self.dispatch(message); } + /// Notifies the backend that the selected nodes have been moved + #[wasm_bindgen(js_name = moveSelectedNodes)] + pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) { + let message = NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y }; + self.dispatch(message); + } + /// Pastes an image #[wasm_bindgen(js_name = pasteImage)] pub fn paste_image(&self, mime: String, image_data: Vec, mouse_x: Option, mouse_y: Option) { diff --git a/graphene/src/layers/nodegraph_layer.rs b/graphene/src/layers/nodegraph_layer.rs index f8301dbe38..17e5206ca9 100644 --- a/graphene/src/layers/nodegraph_layer.rs +++ b/graphene/src/layers/nodegraph_layer.rs @@ -131,6 +131,7 @@ impl Default for NodeGraphFrameLayer { "graphene_std::raster::BrightenImageNode", &[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))], )), + metadata: DocumentNodeMetadata::default(), }, )] .into_iter() @@ -149,6 +150,7 @@ impl Default for NodeGraphFrameLayer { "graphene_std::raster::HueShiftImage", &[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))], )), + metadata: DocumentNodeMetadata::default(), }, )] .into_iter() @@ -167,6 +169,7 @@ impl Default for NodeGraphFrameLayer { name: "Input".into(), inputs: vec![NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), + metadata: DocumentNodeMetadata { position: (8, 4) }, }, ), ( @@ -181,6 +184,7 @@ impl Default for NodeGraphFrameLayer { }, ], implementation: DocumentNodeImplementation::Network(hue_shift_network), + metadata: DocumentNodeMetadata { position: (8 + 7, 4 + 2) }, }, ), ( @@ -195,6 +199,7 @@ impl Default for NodeGraphFrameLayer { }, ], implementation: DocumentNodeImplementation::Network(brighten_network), + metadata: DocumentNodeMetadata { position: (8 + 7 * 2, 4 + 2 * 2) }, }, ), ( @@ -203,6 +208,7 @@ impl Default for NodeGraphFrameLayer { name: "Output".into(), inputs: vec![NodeInput::Node(2)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), + metadata: DocumentNodeMetadata { position: (8 + 7 * 3, 4) }, }, ), ] diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 7c2d7a087b..96ef31ac21 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -28,12 +28,19 @@ fn merge_ids(a: u64, b: u64) -> u64 { hasher.finish() } +#[derive(Clone, Debug, PartialEq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DocumentNodeMetadata { + pub position: (i32, i32), +} + #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct DocumentNode { pub name: String, pub inputs: Vec, pub implementation: DocumentNodeImplementation, + pub metadata: DocumentNodeMetadata, } impl DocumentNode { @@ -183,6 +190,7 @@ impl NodeNetwork { name: name.clone(), inputs: vec![NodeInput::Value { tagged_value, exposed }], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])), + metadata: DocumentNodeMetadata::default(), }; assert!(!self.nodes.contains_key(&new_id)); self.nodes.insert(new_id, value_node); @@ -245,6 +253,7 @@ mod test { name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -253,6 +262,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ] @@ -275,6 +285,7 @@ mod test { name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Network], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -283,6 +294,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::Node(1)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ] @@ -309,6 +321,7 @@ mod test { }, ], implementation: DocumentNodeImplementation::Network(add_network()), + metadata: DocumentNodeMetadata::default(), }, )] .into_iter() @@ -328,6 +341,7 @@ mod test { name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Node(0)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }; let proto_node = document_node.resolve_proto_node(); @@ -393,6 +407,7 @@ mod test { name: "Inc".into(), inputs: vec![NodeInput::Node(11)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -401,6 +416,7 @@ mod test { name: "Cons".into(), inputs: vec![NodeInput::Network, NodeInput::Node(14)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -412,6 +428,7 @@ mod test { exposed: false, }], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -420,6 +437,7 @@ mod test { name: "Add".into(), inputs: vec![NodeInput::Node(10)], implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])), + metadata: DocumentNodeMetadata::default(), }, ), ] From 8c736d3f42b3b2018d1f8605f59bd9f1493e520d Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Thu, 17 Nov 2022 21:04:45 +0000 Subject: [PATCH 14/16] Generate node graph on value change --- .../document/node_graph/node_graph_message_handler.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index b2e2f373ab..0c90d32ab4 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -147,6 +147,7 @@ impl MessageHandler { let Some(network) = self.get_active_network_mut(document) else{ @@ -196,6 +197,7 @@ impl MessageHandler { @@ -238,6 +240,7 @@ impl MessageHandler Date: Thu, 17 Nov 2022 21:25:51 +0000 Subject: [PATCH 15/16] Add expose input message --- .../document/node_graph/node_graph_message.rs | 5 +++++ .../node_graph/node_graph_message_handler.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index 6d9aa2008d..a5b29c8d96 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -21,6 +21,11 @@ pub enum NodeGraphMessage { DeleteNode { node_id: NodeId, }, + ExposeInput { + node_id: NodeId, + input_index: usize, + new_exposed: bool, + }, MoveSelectedNodes { displacement_x: i32, displacement_y: i32, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 0c90d32ab4..800d28b4b6 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -200,6 +200,22 @@ impl MessageHandler { + let Some(network) = self.get_active_network_mut(document) else{ + warn!("No network"); + return; + }; + + let Some(node) = network.nodes.get_mut(&node_id) else { + warn!("No node"); + return; + }; + + if let NodeInput::Value { exposed, .. } = &mut node.inputs[input_index] { + *exposed = new_exposed; + } + Self::send_graph(network, responses); + } NodeGraphMessage::MoveSelectedNodes { displacement_x, displacement_y } => { let Some(network) = self.get_active_network_mut(document) else{ warn!("No network"); From 5cd0932d027c18ed45e360888a0159ec5ecbc6d9 Mon Sep 17 00:00:00 2001 From: 0hypercube <0hypercube@gmail.com> Date: Thu, 17 Nov 2022 21:34:33 +0000 Subject: [PATCH 16/16] Fix tests --- node-graph/graph-craft/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node-graph/graph-craft/src/lib.rs b/node-graph/graph-craft/src/lib.rs index acbfa27e0b..d5818964b4 100644 --- a/node-graph/graph-craft/src/lib.rs +++ b/node-graph/graph-craft/src/lib.rs @@ -74,6 +74,7 @@ mod tests { "graphene_core::structural::ConsNode", &[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))], )), + metadata: DocumentNodeMetadata::default(), }, ), ( @@ -85,6 +86,7 @@ mod tests { "graphene_core::ops::AddNode", &[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))], )), + metadata: DocumentNodeMetadata::default(), }, ), ] @@ -108,6 +110,7 @@ mod tests { }, ], implementation: DocumentNodeImplementation::Network(add_network()), + metadata: DocumentNodeMetadata::default(), }, )] .into_iter()