mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +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:
@@ -1,35 +1,35 @@
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::document::{value::TaggedValue, NodeId};
|
||||
use graph_craft::proto::NodeIdentifier;
|
||||
|
||||
#[remain::sorted]
|
||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NodeGraphMessage {
|
||||
// Messages
|
||||
AddLink {
|
||||
from: NodeId,
|
||||
to: NodeId,
|
||||
to_index: usize,
|
||||
},
|
||||
CloseNodeGraph,
|
||||
ConnectNodesByLink {
|
||||
output_node: u64,
|
||||
input_node: u64,
|
||||
input_node_connector_index: u32,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
CreateNode {
|
||||
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
|
||||
node_id: NodeId,
|
||||
// I don't really know what this is for (perhaps a user identifiable name).
|
||||
name: String,
|
||||
// The node identifier must mach that found in `node-graph/graph-craft/src/node_registry.rs` e.g. "graphene_core::raster::GrayscaleNode
|
||||
identifier: NodeIdentifier,
|
||||
num_inputs: u32,
|
||||
node_type: String,
|
||||
},
|
||||
DeleteNode {
|
||||
node_id: NodeId,
|
||||
},
|
||||
ExposeInput {
|
||||
node_id: NodeId,
|
||||
input_index: usize,
|
||||
new_exposed: bool,
|
||||
},
|
||||
MoveSelectedNodes {
|
||||
displacement_x: i32,
|
||||
displacement_y: i32,
|
||||
},
|
||||
OpenNodeGraph {
|
||||
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::widgets::input_widgets::{NumberInput, NumberInputMode};
|
||||
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
|
||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeInput, NodeNetwork};
|
||||
use graphene::document::Document;
|
||||
use graphene::layers::layer_info::LayerDataType;
|
||||
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
|
||||
mod document_node_types;
|
||||
mod node_properties;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DataType {
|
||||
Raster,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FrontendNode {
|
||||
pub id: graph_craft::document::NodeId,
|
||||
#[serde(rename = "displayName")]
|
||||
pub display_name: String,
|
||||
#[serde(rename = "exposedInputs")]
|
||||
pub exposed_inputs: Vec<DataType>,
|
||||
pub outputs: Vec<DataType>,
|
||||
pub position: (i32, i32),
|
||||
}
|
||||
|
||||
// (link_start, link_end, link_end_input_index)
|
||||
@@ -55,111 +64,11 @@ impl NodeGraphMessageHandler {
|
||||
let network = &node_graph_frame.network;
|
||||
let mut section = Vec::new();
|
||||
for node_id in &self.selected_nodes {
|
||||
let node = *node_id;
|
||||
let Some(document_node) = network.nodes.get(node_id) else {
|
||||
continue;
|
||||
};
|
||||
let name = format!("Node {} Properties", document_node.name);
|
||||
let layout = match &document_node.implementation {
|
||||
DocumentNodeImplementation::Network(_) => match document_node.name.as_str() {
|
||||
"Hue Shift Color" => vec![LayoutGroup::Row {
|
||||
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.push(node_properties::generate_node_properties(document_node, *node_id));
|
||||
}
|
||||
|
||||
section
|
||||
@@ -173,7 +82,7 @@ impl NodeGraphMessageHandler {
|
||||
let links = network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(link_end, node)| node.inputs.iter().enumerate().map(move |(index, input)| (input, link_end, index)))
|
||||
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
|
||||
.filter_map(|(input, &link_end, link_end_input_index)| {
|
||||
if let NodeInput::Node(link_start) = *input {
|
||||
Some(FrontendNodeLink {
|
||||
@@ -192,6 +101,9 @@ impl NodeGraphMessageHandler {
|
||||
nodes.push(FrontendNode {
|
||||
id: *id,
|
||||
display_name: node.name.clone(),
|
||||
exposed_inputs: node.inputs.iter().filter(|input| input.is_exposed()).map(|_| DataType::Raster).collect(),
|
||||
outputs: vec![DataType::Raster],
|
||||
position: node.metadata.position,
|
||||
})
|
||||
}
|
||||
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
|
||||
@@ -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>) {
|
||||
#[remain::sorted]
|
||||
match message {
|
||||
NodeGraphMessage::AddLink { from, to, to_index } => {
|
||||
log::debug!("Connect primary output from node {from} to input of index {to_index} on node {to}.");
|
||||
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
if let Some(to) = network.nodes.get_mut(&to) {
|
||||
// Extend number of inputs if not already large enough
|
||||
if to_index >= to.inputs.len() {
|
||||
to.inputs.extend(((to.inputs.len() - 1)..to_index).map(|_| NodeInput::Network));
|
||||
}
|
||||
to.inputs[to_index] = NodeInput::Node(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::CloseNodeGraph => {
|
||||
if let Some(_old_layer_path) = self.layer_path.take() {
|
||||
info!("Closing node graph");
|
||||
@@ -231,45 +130,103 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||
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}.");
|
||||
|
||||
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 {
|
||||
node_id,
|
||||
name,
|
||||
identifier,
|
||||
num_inputs,
|
||||
} => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
let inner_network = NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
nodes: [(
|
||||
node_id,
|
||||
DocumentNode {
|
||||
name: format!("{}_impl", name),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Unresolved(identifier),
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
network.nodes.insert(
|
||||
node_id,
|
||||
NodeGraphMessage::CreateNode { node_id, node_type } => {
|
||||
let Some(network) = self.get_active_network_mut(document) else{
|
||||
warn!("No network");
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(document_node_type) = document_node_types::resolve_document_node_type(&node_type) else{
|
||||
responses.push_back(DialogMessage::DisplayDialogError { title: "Cannot insert node".to_string(), description: format!("The document node '{node_type}' does not exist in the document node list") }.into());
|
||||
return;
|
||||
};
|
||||
|
||||
let num_inputs = document_node_type.default_inputs.len();
|
||||
|
||||
let inner_network = NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name,
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
name: format!("{}_impl", document_node_type.name),
|
||||
// TODO: Allow inserting nodes that contain other nodes.
|
||||
implementation: DocumentNodeImplementation::Network(inner_network),
|
||||
implementation: DocumentNodeImplementation::Unresolved(document_node_type.identifier.clone()),
|
||||
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
|
||||
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 } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
network.nodes.remove(&node_id);
|
||||
// TODO: Update UI if it is not already updated.
|
||||
Self::send_graph(network, responses);
|
||||
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 } => {
|
||||
@@ -283,20 +240,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||
|
||||
Self::send_graph(network, responses);
|
||||
|
||||
// TODO: Dynamic node library
|
||||
responses.push_back(
|
||||
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(),
|
||||
);
|
||||
let node_types = document_node_types::collect_node_types();
|
||||
responses.push_back(FrontendMessage::UpdateNodeTypes { node_types }.into());
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::SelectNodes { nodes } => {
|
||||
@@ -310,7 +255,8 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||
if input_index >= node.inputs.len() {
|
||||
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network));
|
||||
}
|
||||
node.inputs[input_index] = NodeInput::Value(value);
|
||||
node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false };
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameGenerate.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
})),
|
||||
WidgetHolder::new(Widget::TextLabel(TextLabel {
|
||||
value: LayerDataTypeDiscriminant::from(&layer.data).to_string(),
|
||||
value: match &layer.data {
|
||||
LayerDataType::NodeGraphFrame(_) => "Node Graph Frame".into(),
|
||||
other => LayerDataTypeDiscriminant::from(other).to_string(),
|
||||
},
|
||||
..TextLabel::default()
|
||||
})),
|
||||
WidgetHolder::new(Widget::Separator(Separator {
|
||||
@@ -322,14 +325,16 @@ pub fn register_artwork_layer_properties(
|
||||
LayerDataType::NodeGraphFrame(node_graph_frame) => {
|
||||
let is_graph_open = node_graph_message_handler.layer_path.as_ref().filter(|node_graph| *node_graph == &layer_path).is_some();
|
||||
let selected_nodes = &node_graph_message_handler.selected_nodes;
|
||||
|
||||
let mut properties_sections = vec![
|
||||
node_section_transform(layer, persistent_data),
|
||||
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
|
||||
];
|
||||
if !selected_nodes.is_empty() && is_graph_open {
|
||||
node_graph_message_handler.collate_properties(&node_graph_frame)
|
||||
} else {
|
||||
vec![
|
||||
node_section_transform(layer, persistent_data),
|
||||
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
|
||||
]
|
||||
let parameters_sections = node_graph_message_handler.collate_properties(node_graph_frame);
|
||||
properties_sections.extend(parameters_sections.into_iter());
|
||||
}
|
||||
properties_sections
|
||||
}
|
||||
LayerDataType::Folder(_) => {
|
||||
vec![node_section_transform(layer, persistent_data)]
|
||||
|
||||
@@ -437,7 +437,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
// Execute the node graph
|
||||
|
||||
let mut network = node_graph_frame.network.clone();
|
||||
info!("Network {network:?}");
|
||||
info!("Executing network {network:#?}");
|
||||
|
||||
let stack = borrow_stack::FixedSizeStack::new(256);
|
||||
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();
|
||||
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 {
|
||||
info!("Node {:?}", node);
|
||||
info!("Inserting proto node {:?}", node);
|
||||
graph_craft::node_registry::push_node(node, &stack);
|
||||
}
|
||||
|
||||
use borrow_stack::BorrowStack;
|
||||
use dyn_any::IntoDynAny;
|
||||
use graphene_core::Node;
|
||||
|
||||
let result = unsafe { stack.get().last().unwrap().eval(image.into_dyn()) };
|
||||
let result = *dyn_any::downcast::<Image>(result).unwrap();
|
||||
|
||||
@@ -491,7 +495,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
}
|
||||
Err(description) => responses.push_back(
|
||||
DialogMessage::DisplayDialogError {
|
||||
title: "Failed to update image".to_string(),
|
||||
title: "Failed to update node graph".to_string(),
|
||||
description,
|
||||
}
|
||||
.into(),
|
||||
|
||||
Reference in New Issue
Block a user