mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-28 12:38:11 +08:00
Node graph improvements (#855)
* Selecting multiple nodes * Improve logs * Log bad types in dyn any * Add (broken) node links * New topological sort * Fix reorder ids function * Input and output node * Add nodes that operate on images * Fixups * Show node parameters together with layer properties * New nodes don't crash editor * Fix tests * Node positions backend * Generate node graph on value change * Add expose input message * Fix tests Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
5fe7aba85d
commit
6cd0dbdcd0
Generated
+2
@@ -223,6 +223,7 @@ name = "dyn-any"
|
|||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dyn-any-derive",
|
"dyn-any-derive",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -398,6 +399,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"serde",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graph_craft::document::{value::TaggedValue, NodeId};
|
use graph_craft::document::{value::TaggedValue, NodeId};
|
||||||
use graph_craft::proto::NodeIdentifier;
|
|
||||||
|
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NodeGraphMessage {
|
pub enum NodeGraphMessage {
|
||||||
// Messages
|
// Messages
|
||||||
AddLink {
|
|
||||||
from: NodeId,
|
|
||||||
to: NodeId,
|
|
||||||
to_index: usize,
|
|
||||||
},
|
|
||||||
CloseNodeGraph,
|
CloseNodeGraph,
|
||||||
ConnectNodesByLink {
|
ConnectNodesByLink {
|
||||||
output_node: u64,
|
output_node: u64,
|
||||||
input_node: u64,
|
input_node: u64,
|
||||||
input_node_connector_index: u32,
|
input_node_connector_index: usize,
|
||||||
},
|
},
|
||||||
CreateNode {
|
CreateNode {
|
||||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
// I don't really know what this is for (perhaps a user identifiable name).
|
// I don't really know what this is for (perhaps a user identifiable name).
|
||||||
name: String,
|
node_type: 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,
|
|
||||||
},
|
},
|
||||||
DeleteNode {
|
DeleteNode {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
|
ExposeInput {
|
||||||
|
node_id: NodeId,
|
||||||
|
input_index: usize,
|
||||||
|
new_exposed: bool,
|
||||||
|
},
|
||||||
|
MoveSelectedNodes {
|
||||||
|
displacement_x: i32,
|
||||||
|
displacement_y: i32,
|
||||||
|
},
|
||||||
OpenNodeGraph {
|
OpenNodeGraph {
|
||||||
layer_path: Vec<graphene::LayerId>,
|
layer_path: Vec<graphene::LayerId>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,18 +1,27 @@
|
|||||||
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
|
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||||
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::*;
|
use crate::messages::prelude::*;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork};
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
|
||||||
use graphene::document::Document;
|
use graphene::document::Document;
|
||||||
use graphene::layers::layer_info::LayerDataType;
|
use graphene::layers::layer_info::LayerDataType;
|
||||||
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
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)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendNode {
|
pub struct FrontendNode {
|
||||||
pub id: graph_craft::document::NodeId,
|
pub id: graph_craft::document::NodeId,
|
||||||
#[serde(rename = "displayName")]
|
#[serde(rename = "displayName")]
|
||||||
pub display_name: String,
|
pub display_name: String,
|
||||||
|
#[serde(rename = "exposedInputs")]
|
||||||
|
pub exposed_inputs: Vec<DataType>,
|
||||||
|
pub outputs: Vec<DataType>,
|
||||||
|
pub position: (i32, i32),
|
||||||
}
|
}
|
||||||
|
|
||||||
// (link_start, link_end, link_end_input_index)
|
// (link_start, link_end, link_end_input_index)
|
||||||
@@ -55,111 +64,11 @@ impl NodeGraphMessageHandler {
|
|||||||
let network = &node_graph_frame.network;
|
let network = &node_graph_frame.network;
|
||||||
let mut section = Vec::new();
|
let mut section = Vec::new();
|
||||||
for node_id in &self.selected_nodes {
|
for node_id in &self.selected_nodes {
|
||||||
let node = *node_id;
|
|
||||||
let Some(document_node) = network.nodes.get(node_id) else {
|
let Some(document_node) = network.nodes.get(node_id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let name = format!("Node {} Properties", document_node.name);
|
|
||||||
let layout = match &document_node.implementation {
|
section.push(node_properties::generate_node_properties(document_node, *node_id));
|
||||||
DocumentNodeImplementation::Network(_) => match document_node.name.as_str() {
|
|
||||||
"Hue Shift Color" => 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 Color" => 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 properties 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!("{} requires no properties", document_node.name),
|
|
||||||
..Default::default()
|
|
||||||
}))],
|
|
||||||
}],
|
|
||||||
unknown => {
|
|
||||||
vec![
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
|
|
||||||
value: format!("TODO: {} properties", 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
|
section
|
||||||
@@ -173,7 +82,7 @@ impl NodeGraphMessageHandler {
|
|||||||
let links = network
|
let links = network
|
||||||
.nodes
|
.nodes
|
||||||
.iter()
|
.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)| {
|
.filter_map(|(input, &link_end, link_end_input_index)| {
|
||||||
if let NodeInput::Node(link_start) = *input {
|
if let NodeInput::Node(link_start) = *input {
|
||||||
Some(FrontendNodeLink {
|
Some(FrontendNodeLink {
|
||||||
@@ -192,6 +101,9 @@ impl NodeGraphMessageHandler {
|
|||||||
nodes.push(FrontendNode {
|
nodes.push(FrontendNode {
|
||||||
id: *id,
|
id: *id,
|
||||||
display_name: node.name.clone(),
|
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);
|
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
|
||||||
@@ -204,19 +116,6 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||||||
fn process_message(&mut self, message: NodeGraphMessage, (document, _ipp): (&mut Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
fn process_message(&mut self, message: NodeGraphMessage, (document, _ipp): (&mut Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
|
||||||
#[remain::sorted]
|
#[remain::sorted]
|
||||||
match message {
|
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 => {
|
NodeGraphMessage::CloseNodeGraph => {
|
||||||
if let Some(_old_layer_path) = self.layer_path.take() {
|
if let Some(_old_layer_path) = self.layer_path.take() {
|
||||||
info!("Closing node graph");
|
info!("Closing node graph");
|
||||||
@@ -231,45 +130,103 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||||||
input_node_connector_index,
|
input_node_connector_index,
|
||||||
} => {
|
} => {
|
||||||
log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}.");
|
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;
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||||
}
|
}
|
||||||
NodeGraphMessage::CreateNode {
|
NodeGraphMessage::CreateNode { node_id, node_type } => {
|
||||||
node_id,
|
let Some(network) = self.get_active_network_mut(document) else{
|
||||||
name,
|
warn!("No network");
|
||||||
identifier,
|
return;
|
||||||
num_inputs,
|
};
|
||||||
} => {
|
|
||||||
if let Some(network) = self.get_active_network_mut(document) {
|
let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else{
|
||||||
let inner_network = NodeNetwork {
|
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());
|
||||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
return;
|
||||||
output: 0,
|
};
|
||||||
nodes: [(
|
|
||||||
node_id,
|
let num_inputs = document_node_type.default_inputs.len();
|
||||||
DocumentNode {
|
|
||||||
name: format!("{}_impl", name),
|
let inner_network = NodeNetwork {
|
||||||
// TODO: Allow inserting nodes that contain other nodes.
|
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||||
implementation: DocumentNodeImplementation::Unresolved(identifier),
|
output: 0,
|
||||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
nodes: [(
|
||||||
},
|
0,
|
||||||
)]
|
|
||||||
.into_iter()
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
network.nodes.insert(
|
|
||||||
node_id,
|
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name,
|
name: format!("{}_impl", document_node_type.name),
|
||||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
|
||||||
// TODO: Allow inserting nodes that contain other nodes.
|
// 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(),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
);
|
)]
|
||||||
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),
|
||||||
|
metadata: graph_craft::document::DocumentNodeMetadata {
|
||||||
|
// TODO: Better position default
|
||||||
|
position: (node_id as i32 * 7 - 41, node_id as i32 * 2 - 10),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Self::send_graph(network, responses);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::DeleteNode { node_id } => {
|
NodeGraphMessage::DeleteNode { node_id } => {
|
||||||
if let Some(network) = self.get_active_network_mut(document) {
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
network.nodes.remove(&node_id);
|
network.nodes.remove(&node_id);
|
||||||
// TODO: Update UI if it is not already updated.
|
Self::send_graph(network, responses);
|
||||||
|
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||||
|
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");
|
||||||
|
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 } => {
|
NodeGraphMessage::OpenNodeGraph { layer_path } => {
|
||||||
@@ -283,20 +240,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||||||
|
|
||||||
Self::send_graph(network, responses);
|
Self::send_graph(network, responses);
|
||||||
|
|
||||||
// TODO: Dynamic node library
|
let node_types = document_node_types::collect_node_types();
|
||||||
responses.push_back(
|
responses.push_back(FrontendMessage::UpdateNodeTypes { node_types }.into());
|
||||||
FrontendMessage::UpdateNodeTypes {
|
|
||||||
node_types: vec![
|
|
||||||
FrontendNodeType::new("Identity"),
|
|
||||||
FrontendNodeType::new("Grayscale Color"),
|
|
||||||
FrontendNodeType::new("Brighten Color"),
|
|
||||||
FrontendNodeType::new("Hue Shift Color"),
|
|
||||||
FrontendNodeType::new("Add"),
|
|
||||||
FrontendNodeType::new("Map Image"),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SelectNodes { nodes } => {
|
NodeGraphMessage::SelectNodes { nodes } => {
|
||||||
@@ -310,7 +255,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
|||||||
if input_index >= node.inputs.len() {
|
if input_index >= node.inputs.len() {
|
||||||
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network));
|
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 };
|
||||||
|
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+93
@@ -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<FrontendNodeType> {
|
||||||
|
DOCUMENT_NODE_TYPES.iter().map(|node_type| FrontendNodeType { name: node_type.name.to_string() }).collect()
|
||||||
|
}
|
||||||
+111
@@ -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 }
|
||||||
|
}
|
||||||
@@ -269,7 +269,10 @@ pub fn register_artwork_layer_properties(
|
|||||||
direction: SeparatorDirection::Horizontal,
|
direction: SeparatorDirection::Horizontal,
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
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()
|
..TextLabel::default()
|
||||||
})),
|
})),
|
||||||
WidgetHolder::new(Widget::Separator(Separator {
|
WidgetHolder::new(Widget::Separator(Separator {
|
||||||
@@ -322,14 +325,16 @@ pub fn register_artwork_layer_properties(
|
|||||||
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
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 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 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 {
|
if !selected_nodes.is_empty() && is_graph_open {
|
||||||
node_graph_message_handler.collate_properties(&node_graph_frame)
|
let parameters_sections = node_graph_message_handler.collate_properties(node_graph_frame);
|
||||||
} else {
|
properties_sections.extend(parameters_sections.into_iter());
|
||||||
vec![
|
|
||||||
node_section_transform(layer, persistent_data),
|
|
||||||
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
properties_sections
|
||||||
}
|
}
|
||||||
LayerDataType::Folder(_) => {
|
LayerDataType::Folder(_) => {
|
||||||
vec![node_section_transform(layer, persistent_data)]
|
vec![node_section_transform(layer, persistent_data)]
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||||||
// Execute the node graph
|
// Execute the node graph
|
||||||
|
|
||||||
let mut network = node_graph_frame.network.clone();
|
let mut network = node_graph_frame.network.clone();
|
||||||
info!("Network {network:?}");
|
info!("Executing network {network:#?}");
|
||||||
|
|
||||||
let stack = borrow_stack::FixedSizeStack::new(256);
|
let stack = borrow_stack::FixedSizeStack::new(256);
|
||||||
for node_id in node_graph_frame.network.nodes.keys() {
|
for node_id in node_graph_frame.network.nodes.keys() {
|
||||||
@@ -447,14 +447,18 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||||||
let mut proto_network = network.into_proto_network();
|
let mut proto_network = network.into_proto_network();
|
||||||
proto_network.reorder_ids();
|
proto_network.reorder_ids();
|
||||||
|
|
||||||
|
info!("proto_network with reordered ids: {proto_network:#?}");
|
||||||
|
|
||||||
|
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||||
for (_id, node) in proto_network.nodes {
|
for (_id, node) in proto_network.nodes {
|
||||||
info!("Node {:?}", node);
|
info!("Inserting proto node {:?}", node);
|
||||||
graph_craft::node_registry::push_node(node, &stack);
|
graph_craft::node_registry::push_node(node, &stack);
|
||||||
}
|
}
|
||||||
|
|
||||||
use borrow_stack::BorrowStack;
|
use borrow_stack::BorrowStack;
|
||||||
use dyn_any::IntoDynAny;
|
use dyn_any::IntoDynAny;
|
||||||
use graphene_core::Node;
|
use graphene_core::Node;
|
||||||
|
|
||||||
let result = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) };
|
let result = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) };
|
||||||
let result = *dyn_any::downcast::<Image>(result).unwrap();
|
let result = *dyn_any::downcast::<Image>(result).unwrap();
|
||||||
|
|
||||||
@@ -491,7 +495,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||||||
}
|
}
|
||||||
Err(description) => responses.push_back(
|
Err(description) => responses.push_back(
|
||||||
DialogMessage::DisplayDialogError {
|
DialogMessage::DisplayDialogError {
|
||||||
title: "Failed to update image".to_string(),
|
title: "Failed to update node graph".to_string(),
|
||||||
description,
|
description,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
class="node"
|
class="node"
|
||||||
:class="{ selected: selected.includes(node.id) }"
|
:class="{ selected: selected.includes(node.id) }"
|
||||||
:style="{
|
:style="{
|
||||||
'--offset-left': 8 + Number(node.id < 9n ? node.id : node.id - 9n) * 7,
|
'--offset-left': node.position?.x || 0,
|
||||||
'--offset-top': 4 + Number(node.id < 9n ? node.id : node.id - 9n) * 2,
|
'--offset-top': node.position?.y || 0,
|
||||||
'--data-color': 'var(--color-data-raster)',
|
'--data-color': 'var(--color-data-raster)',
|
||||||
'--data-color-dim': 'var(--color-data-raster-dim)',
|
'--data-color-dim': 'var(--color-data-raster-dim)',
|
||||||
}"
|
}"
|
||||||
@@ -438,11 +438,17 @@ export default defineComponent({
|
|||||||
const nodeId = node?.getAttribute("data-node") || undefined;
|
const nodeId = node?.getAttribute("data-node") || undefined;
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
const id = BigInt(nodeId);
|
const id = BigInt(nodeId);
|
||||||
this.editor.instance.selectNodes(new BigUint64Array([id]));
|
if (e.shiftKey || e.ctrlKey) {
|
||||||
this.selected = [id];
|
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 {
|
} else {
|
||||||
this.editor.instance.selectNodes(new BigUint64Array([]));
|
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
|
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||||
graphDiv?.setPointerCapture(e.pointerId);
|
graphDiv?.setPointerCapture(e.pointerId);
|
||||||
|
|
||||||
@@ -483,9 +489,9 @@ export default defineComponent({
|
|||||||
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
|
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
|
||||||
|
|
||||||
if (inputNodeConnectionIndex !== 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), inputNodeConnectionIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,11 +88,11 @@
|
|||||||
|
|
||||||
.widget-row {
|
.widget-row {
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: -1px;
|
margin-top: calc(4px - 1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: -1px;
|
margin-bottom: calc(4px - 1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
> .text-label:first-of-type {
|
> .text-label:first-of-type {
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export class JsMessage {
|
|||||||
static readonly jsMessageMarker = true;
|
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.
|
// 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;
|
readonly id!: bigint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DataType = "Raster" | "Color" | "Image" | "F32";
|
||||||
|
|
||||||
export class FrontendNode {
|
export class FrontendNode {
|
||||||
readonly id!: bigint;
|
readonly id!: bigint;
|
||||||
|
|
||||||
readonly displayName!: string;
|
readonly displayName!: string;
|
||||||
|
|
||||||
|
readonly exposedInputs!: DataType[];
|
||||||
|
|
||||||
|
readonly outputs!: DataType[];
|
||||||
|
|
||||||
|
@TupleToVec2
|
||||||
|
readonly position!: XY | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FrontendNodeLink {
|
export class FrontendNodeLink {
|
||||||
@@ -403,11 +417,6 @@ export class UpdateDocumentArtboards extends JsMessage {
|
|||||||
readonly svg!: string;
|
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 {
|
export class UpdateDocumentScrollbars extends JsMessage {
|
||||||
@TupleToVec2
|
@TupleToVec2
|
||||||
readonly position!: XY;
|
readonly position!: XY;
|
||||||
|
|||||||
@@ -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
|
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
|
||||||
#[wasm_bindgen(js_name = connectNodesByLink)]
|
#[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 {
|
let message = NodeGraphMessage::ConnectNodesByLink {
|
||||||
output_node,
|
output_node,
|
||||||
input_node,
|
input_node,
|
||||||
@@ -553,9 +553,6 @@ impl JsEditorHandle {
|
|||||||
/// Creates a new document node in the node graph
|
/// Creates a new document node in the node graph
|
||||||
#[wasm_bindgen(js_name = createNode)]
|
#[wasm_bindgen(js_name = createNode)]
|
||||||
pub fn create_node(&self, node_type: String) {
|
pub fn create_node(&self, node_type: String) {
|
||||||
use graph_craft::proto::{NodeIdentifier, Type};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
fn generate_node_id() -> u64 {
|
fn generate_node_id() -> u64 {
|
||||||
static mut NODE_ID: u64 = 10;
|
static mut NODE_ID: u64 = 10;
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -564,24 +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 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),
|
|
||||||
"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),
|
|
||||||
};
|
|
||||||
|
|
||||||
let message = NodeGraphMessage::CreateNode {
|
let message = NodeGraphMessage::CreateNode {
|
||||||
node_id: generate_node_id(),
|
node_id: generate_node_id(),
|
||||||
name: node_type,
|
node_type,
|
||||||
identifier: ident,
|
|
||||||
num_inputs: args,
|
|
||||||
};
|
};
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
@@ -593,6 +575,13 @@ impl JsEditorHandle {
|
|||||||
self.dispatch(message);
|
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
|
/// Pastes an image
|
||||||
#[wasm_bindgen(js_name = pasteImage)]
|
#[wasm_bindgen(js_name = pasteImage)]
|
||||||
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
pub fn paste_image(&self, mime: String, image_data: Vec<u8>, mouse_x: Option<f64>, mouse_y: Option<f64>) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::layers::text_layer::FontCache;
|
|||||||
use crate::LayerId;
|
use crate::LayerId;
|
||||||
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
|
use graph_craft::proto::Type;
|
||||||
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
use kurbo::{Affine, BezPath, Shape as KurboShape};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
@@ -124,12 +125,13 @@ impl Default for NodeGraphFrameLayer {
|
|||||||
nodes: [(
|
nodes: [(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "brighten".into(),
|
name: "Brighten Image Node".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
"graphene_core::raster::BrightenColorNode",
|
"graphene_std::raster::BrightenImageNode",
|
||||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||||
)),
|
)),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -142,12 +144,13 @@ impl Default for NodeGraphFrameLayer {
|
|||||||
nodes: [(
|
nodes: [(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "hue shift".into(),
|
name: "Hue Shift Image Node".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
"graphene_core::raster::HueShiftNode",
|
"graphene_std::raster::HueShiftImage",
|
||||||
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
&[graph_craft::proto::Type::Concrete(std::borrow::Cow::Borrowed("&TypeErasedNode"))],
|
||||||
)),
|
)),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -157,31 +160,55 @@ impl Default for NodeGraphFrameLayer {
|
|||||||
Self {
|
Self {
|
||||||
mime: String::new(),
|
mime: String::new(),
|
||||||
network: NodeNetwork {
|
network: NodeNetwork {
|
||||||
inputs: vec![2, 1],
|
inputs: vec![0],
|
||||||
output: 2,
|
output: 3,
|
||||||
nodes: [
|
nodes: [
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Hue Shift Color".into(),
|
name: "Input".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Value(value::TaggedValue::F32(50.))],
|
inputs: vec![NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata { position: (8, 4) },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Brighten Color".into(),
|
name: "Hue Shift Image".into(),
|
||||||
inputs: vec![NodeInput::Node(0), NodeInput::Value(value::TaggedValue::F32(10.))],
|
inputs: vec![
|
||||||
implementation: DocumentNodeImplementation::Network(brighten_network),
|
NodeInput::Node(0),
|
||||||
|
NodeInput::Value {
|
||||||
|
tagged_value: value::TaggedValue::F32(50.),
|
||||||
|
exposed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
implementation: DocumentNodeImplementation::Network(hue_shift_network),
|
||||||
|
metadata: DocumentNodeMetadata { position: (8 + 7, 4 + 2) },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
2,
|
2,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Map Image".into(),
|
name: "Brighten Image".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Node(1)],
|
inputs: vec![
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[])),
|
NodeInput::Node(1),
|
||||||
|
NodeInput::Value {
|
||||||
|
tagged_value: value::TaggedValue::F32(10.),
|
||||||
|
exposed: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
implementation: DocumentNodeImplementation::Network(brighten_network),
|
||||||
|
metadata: DocumentNodeMetadata { position: (8 + 7 * 2, 4 + 2 * 2) },
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
3,
|
||||||
|
DocumentNode {
|
||||||
|
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) },
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ documentation = "https://docs.rs/dyn-any"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
dyn-any-derive = { path = "derive", version = "0.2.0", optional = true }
|
||||||
|
log = { version = "0.4", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
derive = ["dyn-any-derive"]
|
derive = ["dyn-any-derive"]
|
||||||
|
log-bad-types = ["log"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -50,12 +50,18 @@ impl<'a, T: DynAny<'a> + 'a> UpcastFrom<T> for dyn DynAny<'a> + 'a {
|
|||||||
|
|
||||||
pub trait DynAny<'a> {
|
pub trait DynAny<'a> {
|
||||||
fn type_id(&self) -> TypeId;
|
fn type_id(&self) -> TypeId;
|
||||||
|
#[cfg(feature = "log-bad-types")]
|
||||||
|
fn type_name(&self) -> &'static str;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: StaticType> DynAny<'a> for T {
|
impl<'a, T: StaticType> DynAny<'a> for T {
|
||||||
fn type_id(&self) -> std::any::TypeId {
|
fn type_id(&self) -> std::any::TypeId {
|
||||||
std::any::TypeId::of::<T::Static>()
|
std::any::TypeId::of::<T::Static>()
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "log-bad-types")]
|
||||||
|
fn type_name(&self) -> &'static str {
|
||||||
|
std::any::type_name::<T>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
|
pub fn downcast_ref<'a, V: StaticType>(i: &'a dyn DynAny<'a>) -> Option<&'a V> {
|
||||||
if i.type_id() == std::any::TypeId::of::<<V as StaticType>::Static>() {
|
if i.type_id() == std::any::TypeId::of::<<V as StaticType>::Static>() {
|
||||||
@@ -74,7 +80,11 @@ pub fn downcast<'a, V: StaticType>(i: Box<dyn DynAny<'a> + 'a>) -> Option<Box<V>
|
|||||||
let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V;
|
let ptr = Box::into_raw(i) as *mut dyn DynAny<'a> as *mut V;
|
||||||
Some(unsafe { Box::from_raw(ptr) })
|
Some(unsafe { Box::from_raw(ptr) })
|
||||||
} else {
|
} 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::<V>());
|
||||||
|
}
|
||||||
|
|
||||||
if type_id == std::any::TypeId::of::<&dyn DynAny<'static>>() {
|
if type_id == std::any::TypeId::of::<&dyn DynAny<'static>>() {
|
||||||
panic!("downcast error: type_id == std::any::TypeId::of::<dyn DynAny<'a>>()");
|
panic!("downcast error: type_id == std::any::TypeId::of::<dyn DynAny<'a>>()");
|
||||||
}
|
}
|
||||||
@@ -192,9 +202,12 @@ impl_type!(Option<T>,Result<T, E>,Cell<T>,UnsafeCell<T>,RefCell<T>,MaybeUninit<T
|
|||||||
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
impl<T: crate::StaticType + ?Sized> crate::StaticType for Box<T> {
|
||||||
type Static = Box<<T as crate::StaticType>::Static>;
|
type Static = Box<<T as crate::StaticType>::Static>;
|
||||||
}
|
}
|
||||||
fn test() {
|
#[test]
|
||||||
let foo = (Box::new(&1 as &dyn DynAny<'static>), Box::new(&2 as &dyn DynAny<'static>));
|
fn test_tuple_of_boxes() {
|
||||||
//let bar = &foo as &dyn DynAny<'static>;
|
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 {
|
macro_rules! impl_tuple {
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ use self::color::Color;
|
|||||||
pub mod color;
|
pub mod color;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct GrayscaleNode;
|
pub struct GrayscaleColorNode;
|
||||||
|
|
||||||
impl Node<Color> for GrayscaleNode {
|
impl Node<Color> for GrayscaleColorNode {
|
||||||
type Output = Color;
|
type Output = Color;
|
||||||
fn eval(self, color: Color) -> Color {
|
fn eval(self, color: Color) -> Color {
|
||||||
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
||||||
Color::from_rgbaf32_unchecked(avg, avg, avg, color.a())
|
Color::from_rgbaf32_unchecked(avg, avg, avg, color.a())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'n> Node<Color> for &'n GrayscaleNode {
|
impl<'n> Node<Color> for &'n GrayscaleColorNode {
|
||||||
type Output = Color;
|
type Output = Color;
|
||||||
fn eval(self, color: Color) -> Color {
|
fn eval(self, color: Color) -> Color {
|
||||||
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
let avg = (color.r() + color.g() + color.b()) / 3.0;
|
||||||
@@ -49,9 +49,9 @@ impl<N: Node<(), Output = f32> + Copy> BrightenColorNode<N> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct HueShiftNode<N: Node<(), Output = f32>>(N);
|
pub struct HueShiftColorNode<N: Node<(), Output = f32>>(N);
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftNode<N> {
|
impl<N: Node<(), Output = f32>> Node<Color> for HueShiftColorNode<N> {
|
||||||
type Output = Color;
|
type Output = Color;
|
||||||
fn eval(self, color: Color) -> Color {
|
fn eval(self, color: Color) -> Color {
|
||||||
let hue_shift = self.0.eval(());
|
let hue_shift = self.0.eval(());
|
||||||
@@ -59,7 +59,7 @@ impl<N: Node<(), Output = f32>> Node<Color> for HueShiftNode<N> {
|
|||||||
Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha)
|
Color::from_hsla(hue + hue_shift / 360., saturation, luminance, alpha)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftNode<N> {
|
impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftColorNode<N> {
|
||||||
type Output = Color;
|
type Output = Color;
|
||||||
fn eval(self, color: Color) -> Color {
|
fn eval(self, color: Color) -> Color {
|
||||||
let hue_shift = self.0.eval(());
|
let hue_shift = self.0.eval(());
|
||||||
@@ -68,7 +68,7 @@ impl<N: Node<(), Output = f32> + Copy> Node<Color> for &HueShiftNode<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: Node<(), Output = f32> + Copy> HueShiftNode<N> {
|
impl<N: Node<(), Output = f32> + Copy> HueShiftColorNode<N> {
|
||||||
pub fn new(node: N) -> Self {
|
pub fn new(node: N) -> Self {
|
||||||
Self(node)
|
Self(node)
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn map_node() {
|
fn map_node() {
|
||||||
// let array = &mut [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
// 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));
|
/*let map = ForEachNode(MutWrapper(GrayscaleNode));
|
||||||
(&map).eval(array.iter_mut());
|
(&map).eval(array.iter_mut());
|
||||||
assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/
|
assert_eq!(array[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());*/
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ license = "MIT OR Apache-2.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
graphene-core = { path = "../gcore", features = ["async", "std"] }
|
||||||
graphene-std = { path = "../gstd" }
|
graphene-std = { path = "../gstd" }
|
||||||
dyn-any = { path = "../../libraries/dyn-any" }
|
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] }
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
borrow_stack = { path = "../borrow_stack" }
|
borrow_stack = { path = "../borrow_stack" }
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
[dependencies.serde]
|
[features]
|
||||||
version = "1.0"
|
serde = ["dep:serde", "graphene-std/serde"]
|
||||||
optional = true
|
|
||||||
features = ["derive"]
|
|
||||||
|
|||||||
@@ -28,12 +28,19 @@ fn merge_ids(a: u64, b: u64) -> u64 {
|
|||||||
hasher.finish()
|
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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct DocumentNode {
|
pub struct DocumentNode {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub inputs: Vec<NodeInput>,
|
pub inputs: Vec<NodeInput>,
|
||||||
pub implementation: DocumentNodeImplementation,
|
pub implementation: DocumentNodeImplementation,
|
||||||
|
pub metadata: DocumentNodeMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DocumentNode {
|
impl DocumentNode {
|
||||||
@@ -54,7 +61,7 @@ impl DocumentNode {
|
|||||||
let first = self.inputs.remove(0);
|
let first = self.inputs.remove(0);
|
||||||
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
|
||||||
let (input, mut args) = match first {
|
let (input, mut args) = match first {
|
||||||
NodeInput::Value(tagged_value) => {
|
NodeInput::Value { tagged_value, .. } => {
|
||||||
assert_eq!(self.inputs.len(), 0);
|
assert_eq!(self.inputs.len(), 0);
|
||||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value.to_value()))
|
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value.to_value()))
|
||||||
}
|
}
|
||||||
@@ -63,7 +70,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::Network)), "recieved non resolved parameter");
|
||||||
assert!(
|
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: {:#?}",
|
"recieved value as parameter. inupts: {:#?}, construction_args: {:#?}",
|
||||||
&self.inputs,
|
&self.inputs,
|
||||||
&args
|
&args
|
||||||
@@ -90,7 +97,7 @@ impl DocumentNode {
|
|||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum NodeInput {
|
pub enum NodeInput {
|
||||||
Node(NodeId),
|
Node(NodeId),
|
||||||
Value(value::TaggedValue),
|
Value { tagged_value: value::TaggedValue, exposed: bool },
|
||||||
Network,
|
Network,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +107,20 @@ impl NodeInput {
|
|||||||
*self = NodeInput::Node(f(*id))
|
*self = NodeInput::Node(f(*id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn is_exposed(&self) -> bool {
|
||||||
|
if let NodeInput::Value { exposed, .. } = self {
|
||||||
|
*exposed
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for NodeInput {
|
impl PartialEq for NodeInput {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
match (&self, &other) {
|
match (&self, &other) {
|
||||||
(Self::Node(n1), Self::Node(n2)) => n1 == n2,
|
(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),
|
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,13 +183,14 @@ impl NodeNetwork {
|
|||||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||||
network_input.populate_first_network_input(node, *offset);
|
network_input.populate_first_network_input(node, *offset);
|
||||||
}
|
}
|
||||||
NodeInput::Value(value) => {
|
NodeInput::Value { tagged_value, exposed } => {
|
||||||
let name = format!("Value: {:?}", value.clone().to_value());
|
let name = format!("Value: {:?}", tagged_value.clone().to_value());
|
||||||
let new_id = map_ids(id, gen_id());
|
let new_id = map_ids(id, gen_id());
|
||||||
let value_node = DocumentNode {
|
let value_node = DocumentNode {
|
||||||
name: name.clone(),
|
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])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
};
|
};
|
||||||
assert!(!self.nodes.contains_key(&new_id));
|
assert!(!self.nodes.contains_key(&new_id));
|
||||||
self.nodes.insert(new_id, value_node);
|
self.nodes.insert(new_id, value_node);
|
||||||
@@ -235,17 +250,19 @@ mod test {
|
|||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "cons".into(),
|
name: "Cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "add".into(),
|
name: "Add".into(),
|
||||||
inputs: vec![NodeInput::Node(0)],
|
inputs: vec![NodeInput::Node(0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -265,17 +282,19 @@ mod test {
|
|||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "cons".into(),
|
name: "Cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
2,
|
2,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "add".into(),
|
name: "Add".into(),
|
||||||
inputs: vec![NodeInput::Node(1)],
|
inputs: vec![NodeInput::Node(1)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -294,8 +313,15 @@ mod test {
|
|||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Inc".into(),
|
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()),
|
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -312,9 +338,10 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn resolve_proto_node_add() {
|
fn resolve_proto_node_add() {
|
||||||
let document_node = DocumentNode {
|
let document_node = DocumentNode {
|
||||||
name: "cons".into(),
|
name: "Cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Node(0)],
|
inputs: vec![NodeInput::Network, NodeInput::Node(0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let proto_node = document_node.resolve_proto_node();
|
let proto_node = document_node.resolve_proto_node();
|
||||||
@@ -380,30 +407,37 @@ mod test {
|
|||||||
name: "Inc".into(),
|
name: "Inc".into(),
|
||||||
inputs: vec![NodeInput::Node(11)],
|
inputs: vec![NodeInput::Node(11)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
10,
|
10,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "cons".into(),
|
name: "Cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Node(14)],
|
inputs: vec![NodeInput::Network, NodeInput::Node(14)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
14,
|
14,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Value: 2".into(),
|
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])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::value::ValueNode", &[Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
11,
|
11,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "add".into(),
|
name: "Add".into(),
|
||||||
inputs: vec![NodeInput::Node(10)],
|
inputs: vec![NodeInput::Node(10)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[Type::Generic, Type::Generic])),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub enum TaggedValue {
|
|||||||
String(String),
|
String(String),
|
||||||
U32(u32),
|
U32(u32),
|
||||||
F32(f32),
|
F32(f32),
|
||||||
//Image(graphene_std::raster::Image),
|
Image(graphene_std::raster::Image),
|
||||||
Color(graphene_core::raster::color::Color),
|
Color(graphene_core::raster::color::Color),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ impl TaggedValue {
|
|||||||
TaggedValue::String(x) => Box::new(x),
|
TaggedValue::String(x) => Box::new(x),
|
||||||
TaggedValue::U32(x) => Box::new(x),
|
TaggedValue::U32(x) => Box::new(x),
|
||||||
TaggedValue::F32(x) => Box::new(x),
|
TaggedValue::F32(x) => Box::new(x),
|
||||||
|
TaggedValue::Image(x) => Box::new(x),
|
||||||
TaggedValue::Color(x) => Box::new(x),
|
TaggedValue::Color(x) => Box::new(x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,23 +68,25 @@ mod tests {
|
|||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "cons".into(),
|
name: "Cons".into(),
|
||||||
inputs: vec![NodeInput::Network, NodeInput::Network],
|
inputs: vec![NodeInput::Network, NodeInput::Network],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
"graphene_core::structural::ConsNode",
|
"graphene_core::structural::ConsNode",
|
||||||
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
)),
|
)),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "add".into(),
|
name: "Add".into(),
|
||||||
inputs: vec![NodeInput::Node(0)],
|
inputs: vec![NodeInput::Node(0)],
|
||||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new(
|
||||||
"graphene_core::ops::AddNode",
|
"graphene_core::ops::AddNode",
|
||||||
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
&[Type::Concrete(std::borrow::Cow::Borrowed("u32")), Type::Concrete(std::borrow::Cow::Borrowed("u32"))],
|
||||||
)),
|
)),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -100,8 +102,15 @@ mod tests {
|
|||||||
0,
|
0,
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
name: "Inc".into(),
|
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()),
|
implementation: DocumentNodeImplementation::Network(add_network()),
|
||||||
|
metadata: DocumentNodeMetadata::default(),
|
||||||
},
|
},
|
||||||
)]
|
)]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -21,17 +21,25 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||||||
NodeIdentifier::new("graphene_core::ops::IdNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]),
|
NodeIdentifier::new("graphene_core::ops::IdNode", &[Concrete(std::borrow::Cow::Borrowed("Any<'_>"))]),
|
||||||
|proto_node, stack| {
|
|proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||||
node.into_type_erased()
|
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| {
|
(NodeIdentifier::new("graphene_core::ops::IdNode", &[Type::Generic]), |proto_node, stack| {
|
||||||
stack.push_fn(|nodes| {
|
stack.push_fn(|nodes| {
|
||||||
let pre_node = nodes.get(proto_node.input.unwrap_node() as usize).unwrap();
|
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||||
let node = pre_node.then(graphene_core::ops::IdNode);
|
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||||
node.into_type_erased()
|
let node = pre_node.then(graphene_core::ops::IdNode);
|
||||||
|
node.into_type_erased()
|
||||||
|
} else {
|
||||||
|
graphene_core::ops::IdNode.into_type_erased()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
(
|
(
|
||||||
@@ -190,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| {
|
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 {
|
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||||
@@ -222,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| {
|
|proto_node, stack| {
|
||||||
info!("proto node {:?}", proto_node);
|
info!("proto node {:?}", proto_node);
|
||||||
stack.push_fn(|nodes| {
|
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 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 value_node = nodes.get(construction_nodes[0] as usize).unwrap();
|
||||||
let input_node: DowncastBothNode<_, (), f32> = DowncastBothNode::new(value_node);
|
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 {
|
if let ProtoNodeInput::Node(pre_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(pre_id as usize).unwrap();
|
let pre_node = nodes.get(pre_id as usize).unwrap();
|
||||||
@@ -243,6 +251,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||||||
(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), |proto_node, stack| {
|
(NodeIdentifier::new("graphene_std::raster::MapImageNode", &[]), |proto_node, stack| {
|
||||||
if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args {
|
if let ConstructionArgs::Nodes(operation_node_id) = proto_node.construction_args {
|
||||||
stack.push_fn(move |nodes| {
|
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 = nodes.get(operation_node_id[0] as usize).unwrap();
|
||||||
let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node);
|
let operation_node: DowncastBothNode<_, Color, Color> = DowncastBothNode::new(operation_node);
|
||||||
let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node));
|
let map_node = DynAnyNode::new(graphene_std::raster::MapImageNode::new(operation_node));
|
||||||
@@ -258,45 +267,54 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||||||
unimplemented!()
|
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| {
|
stack.push_fn(move |nodes| {
|
||||||
let grayscale_node = DynAnyNode::new(FnNode::new(|mut image: Image| {
|
let node = DynAnyNode::new(graphene_std::raster::GrayscaleImageNode);
|
||||||
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
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
let pre_node = nodes.get(node_id as usize).unwrap();
|
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 {
|
} else {
|
||||||
grayscale_node.into_type_erased()
|
node.into_type_erased()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
(NodeIdentifier::new("graph_craft::node_registry::HueShiftImage", &[]), |_proto_node, _stack| {
|
(
|
||||||
todo!();
|
NodeIdentifier::new("graphene_std::raster::HueShiftImage", &[Type::Concrete(Cow::Borrowed("&TypeErasedNode"))]),
|
||||||
// stack.push_fn(move |nodes| {
|
|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));
|
||||||
|
|
||||||
// let hue_shift_node = DynAnyNode::new(FnNode::new(|(mut image, amount): (Image, f32)| {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
// for pixel in &mut image.data {
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
// let [mut hue, saturation, luminance, alpha] = pixel.to_hsla();
|
(pre_node).then(node).into_type_erased()
|
||||||
// hue += amount;
|
} else {
|
||||||
// *pixel = Color::from_hsla(hue, saturation, luminance, alpha);
|
node.into_type_erased()
|
||||||
// }
|
}
|
||||||
// image
|
})
|
||||||
// }));
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
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 {
|
if let ProtoNodeInput::Node(node_id) = proto_node.input {
|
||||||
// let pre_node = nodes.get(node_id as usize).unwrap();
|
let pre_node = nodes.get(node_id as usize).unwrap();
|
||||||
// (pre_node).then(hue_shift_node).into_type_erased()
|
(pre_node).then(node).into_type_erased()
|
||||||
// } else {
|
} else {
|
||||||
// hue_shift_node.into_type_erased()
|
node.into_type_erased()
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
NodeIdentifier::new("graphene_std::raster::ImageNode", &[Concrete(std::borrow::Cow::Borrowed("&str"))]),
|
||||||
|_proto_node, stack| {
|
|_proto_node, stack| {
|
||||||
@@ -401,7 +419,7 @@ mod protograph_testing {
|
|||||||
let grayscale_protonode = ProtoNode {
|
let grayscale_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
input: ProtoNodeInput::Node(0),
|
input: ProtoNodeInput::Node(0),
|
||||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]),
|
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]),
|
||||||
};
|
};
|
||||||
push_node(grayscale_protonode, &stack);
|
push_node(grayscale_protonode, &stack);
|
||||||
|
|
||||||
@@ -438,7 +456,7 @@ mod protograph_testing {
|
|||||||
let grayscale_protonode = ProtoNode {
|
let grayscale_protonode = ProtoNode {
|
||||||
construction_args: ConstructionArgs::Nodes(vec![]),
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
input: ProtoNodeInput::None,
|
input: ProtoNodeInput::None,
|
||||||
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleNode", &[]),
|
identifier: NodeIdentifier::new("graphene_core::raster::GrayscaleColorNode", &[]),
|
||||||
};
|
};
|
||||||
push_node(grayscale_protonode, &stack);
|
push_node(grayscale_protonode, &stack);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::document::value;
|
use crate::document::value;
|
||||||
use crate::document::NodeId;
|
use crate::document::NodeId;
|
||||||
@@ -165,7 +165,37 @@ impl ProtoNetwork {
|
|||||||
edges
|
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<NodeId> {
|
||||||
|
let mut sorted = Vec::new();
|
||||||
|
let inwards_edges = self.collect_inwards_edges();
|
||||||
|
fn visit(node_id: NodeId, temp_marks: &mut HashSet<NodeId>, sorted: &mut Vec<NodeId>, inwards_edges: &HashMap<NodeId, Vec<NodeId>>) {
|
||||||
|
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<NodeId> {
|
pub fn topological_sort(&self) -> Vec<NodeId> {
|
||||||
let mut sorted = Vec::new();
|
let mut sorted = Vec::new();
|
||||||
let outwards_edges = self.collect_outwards_edges();
|
let outwards_edges = self.collect_outwards_edges();
|
||||||
@@ -187,21 +217,24 @@ impl ProtoNetwork {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
info!("Sorted order {sorted:?}");
|
||||||
sorted
|
sorted
|
||||||
}
|
}*/
|
||||||
|
|
||||||
pub fn reorder_ids(&mut self) {
|
pub fn reorder_ids(&mut self) {
|
||||||
let order = self.topological_sort();
|
let order = self.topological_sort();
|
||||||
let lookup = self
|
// Map of node ids to indexes (which become the node ids as they are inserted into the borrow stack)
|
||||||
.nodes
|
let lookup: HashMap<_, _> = order.iter().enumerate().map(|(pos, id)| (*id, pos as NodeId)).collect();
|
||||||
|
info!("Order {order:?}");
|
||||||
|
self.nodes = order
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, _)| (*id, order.iter().position(|x| x == id).unwrap() as u64))
|
.map(|id| {
|
||||||
.collect::<HashMap<u64, u64>>();
|
let mut node = self.nodes.swap_remove(self.nodes.iter().position(|(test_id, _)| test_id == id).unwrap()).1;
|
||||||
self.nodes.sort_by_key(|(id, _)| lookup.get(id).unwrap());
|
node.map_ids(|id| *lookup.get(&id).unwrap());
|
||||||
self.nodes.iter_mut().for_each(|(id, node)| {
|
(*lookup.get(id).unwrap(), node)
|
||||||
node.map_ids(|id| *lookup.get(&id).unwrap());
|
})
|
||||||
*id = *lookup.get(id).unwrap()
|
.collect();
|
||||||
});
|
assert_eq!(order.len(), self.nodes.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +250,14 @@ mod test {
|
|||||||
inputs: vec![10],
|
inputs: vec![10],
|
||||||
output: 1,
|
output: 1,
|
||||||
nodes: [
|
nodes: [
|
||||||
|
(
|
||||||
|
7,
|
||||||
|
ProtoNode {
|
||||||
|
identifier: "id".into(),
|
||||||
|
input: ProtoNodeInput::Node(11),
|
||||||
|
construction_args: ConstructionArgs::Nodes(vec![]),
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
1,
|
1,
|
||||||
ProtoNode {
|
ProtoNode {
|
||||||
|
|||||||
@@ -26,3 +26,8 @@ proc-macro2 = {version = "1.0", default-features = false, features = ["proc-macr
|
|||||||
quote = {version = "1.0", default-features = false }
|
quote = {version = "1.0", default-features = false }
|
||||||
image = "*"
|
image = "*"
|
||||||
dyn-clone = "1.0"
|
dyn-clone = "1.0"
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0"
|
||||||
|
optional = true
|
||||||
|
features = ["derive"]
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ impl<Reader: std::io::Read> Node<Reader> for BufferNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, DynAny)]
|
#[derive(Clone, Debug, PartialEq, DynAny)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
@@ -165,16 +166,104 @@ pub fn export_image_node<'n>() -> impl Node<(Image, &'n str), Output = Result<()
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct GrayscaleImageNode;
|
||||||
|
|
||||||
|
impl Node<Image> 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<Image> 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: Node<(), Output = f32>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f32>> Node<Image> for BrightenImageNode<N> {
|
||||||
|
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<N: Node<(), Output = f32> + Copy> Node<Image> for &BrightenImageNode<N> {
|
||||||
|
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<N: Node<(), Output = f32> + Copy> BrightenImageNode<N> {
|
||||||
|
pub fn new(node: N) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct HueShiftImage<N: Node<(), Output = f32>>(N);
|
||||||
|
|
||||||
|
impl<N: Node<(), Output = f32>> Node<Image> for HueShiftImage<N> {
|
||||||
|
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<N: Node<(), Output = f32> + Copy> Node<Image> for &HueShiftImage<N> {
|
||||||
|
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<N: Node<(), Output = f32> + Copy> HueShiftImage<N> {
|
||||||
|
pub fn new(node: N) -> Self {
|
||||||
|
Self(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
use graphene_core::raster::color::Color;
|
use graphene_core::raster::color::Color;
|
||||||
use graphene_core::raster::GrayscaleNode;
|
use graphene_core::raster::GrayscaleColorNode;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_node() {
|
fn map_node() {
|
||||||
let array = [Color::from_rgbaf32(1.0, 0.0, 0.0, 1.0).unwrap()];
|
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());
|
let values = map.eval(array.into_iter());
|
||||||
assert_eq!(values[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());
|
assert_eq!(values[0], Color::from_rgbaf32(0.33333334, 0.33333334, 0.33333334, 1.0).unwrap());
|
||||||
}
|
}
|
||||||
@@ -182,7 +271,7 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn load_image() {
|
fn load_image() {
|
||||||
let image = image_node::<&str>();
|
let image = image_node::<&str>();
|
||||||
let gray = MapImageNode::new(GrayscaleNode);
|
let gray = MapImageNode::new(GrayscaleColorNode);
|
||||||
|
|
||||||
let grayscale_picture = image.then(MapResultNode::new(&gray));
|
let grayscale_picture = image.then(MapResultNode::new(&gray));
|
||||||
let export = export_image_node();
|
let export = export_image_node();
|
||||||
|
|||||||
Reference in New Issue
Block a user