From 8803cb4079b2fc24716b01b0dca97ec81ae840cb Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 5 Jul 2025 12:35:36 -0700 Subject: [PATCH 1/8] Identity from value input --- .../node_graph/node_graph_message_handler.rs | 63 +++++++++++++++++++ .../utility_types/network_interface.rs | 18 ++++++ 2 files changed, 81 insertions(+) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index f28d9d4e11..6dd371e49c 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -994,6 +994,69 @@ impl<'a> MessageHandler> for NodeG }; responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); } + } else if self.disconnecting.is_some() { + // Disconnecting with no upstream node, create new value node. + let to_connector = network_interface.input_connector_from_click(ipp.mouse.position, selection_network_path); + if let Some(to_connector) = &to_connector { + let Some(input_position) = network_interface.input_position(to_connector, selection_network_path) else { + log::error!("Could not get input position for connector: {to_connector:?}"); + return; + }; + self.wire_in_progress_to_connector = Some(input_position); + } + // Not hovering over a node input or node output, insert the node + else { + // Disconnect if the wire was previously connected to an input + if let Some(disconnecting) = self.disconnecting.take() { + let mut position = if let Some(to_connector) = self.wire_in_progress_to_connector { to_connector } else { point }; + // Offset to drag from center of node + position = position - DVec2::new(24. * 3., 24.); + + // Offset to account for division rounding error + if position.x < 0. { + position.x = position.x - 1.; + } + if position.y < 0. { + position.y = position.y - 1.; + } + + let Some(input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else { + return; + }; + + let drag_start = DragStart { + start_x: point.x, + start_y: point.y, + round_x: 0, + round_y: 0, + }; + + self.drag_start = Some((drag_start, false)); + self.node_has_moved_in_drag = false; + self.update_node_graph_hints(responses); + + let node_id = NodeId::new(); + responses.add(NodeGraphMessage::CreateNodeFromContextMenu { + node_id: Some(node_id), + node_type: "Identity".to_string(), + xy: Some(((position.x / 24.) as i32, (position.y / 24.) as i32)), + }); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 0), + input, + }); + + responses.add(NodeGraphMessage::CreateWire { + output_connector: OutputConnector::Node { node_id, output_index: 0 }, + input_connector: disconnecting, + }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }); + // Update the frontend that the node is disconnected + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SendGraph); + } + } } else if let Some((drag_start, dragged)) = &mut self.drag_start { if drag_start.start_x != point.x || drag_start.start_y != point.y { *dragged = true; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 290247f621..18fbfbd274 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -501,6 +501,24 @@ impl NodeNetworkInterface { } } + pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get network in input_from_connector"); + return None; + }; + let input = match input_connector { + InputConnector::Node { node_id, input_index } => { + let Some(node) = network.nodes.get_mut(node_id) else { + log::error!("Could not get node {node_id} in input_from_connector"); + return None; + }; + node.inputs.get_mut(*input_index) + } + InputConnector::Export(export_index) => network.exports.get_mut(*export_index), + }; + input.map(|input| std::mem::replace(input, NodeInput::value(TaggedValue::None, true))) + } + /// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`. fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { let (node_id, input_index) = match *input_connector { From 007812077bf9a67574469139e3ec6615cee3d7df Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 5 Jul 2025 20:33:50 -0700 Subject: [PATCH 2/8] General purpose value node --- .../node_graph/document_node_definitions.rs | 20 +++ .../document/node_graph/node_graph_message.rs | 4 + .../node_graph/node_graph_message_handler.rs | 155 +++++++++++------- .../document/node_graph/node_properties.rs | 97 +++++++++-- .../utility_types/network_interface.rs | 12 +- node-graph/gcore/src/value.rs | 44 ++--- node-graph/graph-craft/src/document/value.rs | 75 ++++++++- node-graph/gstd/src/any.rs | 18 ++ .../interpreted-executor/src/node_registry.rs | 12 +- 9 files changed, 342 insertions(+), 95 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index d24ebaa23b..900bd3a6df 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -102,6 +102,25 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."), properties: Some("identity_properties"), }, + DocumentNodeDefinition { + identifier: "Value", + category: "General", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::proto("graphene_core::any::ValueNode"), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::None, false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("", "Value").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Returns the value stored in its input"), + properties: Some("value_properties"), + }, // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { identifier: "Monitor", @@ -1906,6 +1925,7 @@ fn static_node_properties() -> NodeProperties { "monitor_properties".to_string(), Box::new(|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it.")), ); + map.insert("value_properties".to_string(), Box::new(node_properties::value_properties)); map } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index bd6c77d467..ec76851949 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -152,6 +152,10 @@ pub enum NodeGraphMessage { node_id: NodeId, alias: String, }, + SetReference { + node_id: NodeId, + reference: Option, + }, SetToNodeOrLayer { node_id: NodeId, is_layer: bool, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 6dd371e49c..416b582934 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -537,7 +537,10 @@ impl<'a> MessageHandler> for NodeG log::error!("Could not get center of selected_nodes"); return; }; - let center_of_selected_nodes_grid_space = IVec2::new((center_of_selected_nodes.x / 24. + 0.5).floor() as i32, (center_of_selected_nodes.y / 24. + 0.5).floor() as i32); + let center_of_selected_nodes_grid_space = IVec2::new( + (center_of_selected_nodes.x / GRID_SIZE as f64 + 0.5).floor() as i32, + (center_of_selected_nodes.y / GRID_SIZE as f64 + 0.5).floor() as i32, + ); default_node_template.persistent_node_metadata.node_type_metadata = NodeTypePersistentMetadata::node(center_of_selected_nodes_grid_space - IVec2::new(3, 1)); responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::InsertNode { @@ -645,7 +648,7 @@ impl<'a> MessageHandler> for NodeG log::error!("Could not get network metadata in PointerDown"); return; }; - + self.disconnecting = None; let click = ipp.mouse.position; let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click); @@ -994,8 +997,9 @@ impl<'a> MessageHandler> for NodeG }; responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); } - } else if self.disconnecting.is_some() { - // Disconnecting with no upstream node, create new value node. + } + // Dragging from an exposed value input + else if self.disconnecting.is_some() { let to_connector = network_interface.input_connector_from_click(ipp.mouse.position, selection_network_path); if let Some(to_connector) = &to_connector { let Some(input_position) = network_interface.input_position(to_connector, selection_network_path) else { @@ -1004,58 +1008,12 @@ impl<'a> MessageHandler> for NodeG }; self.wire_in_progress_to_connector = Some(input_position); } - // Not hovering over a node input or node output, insert the node - else { - // Disconnect if the wire was previously connected to an input - if let Some(disconnecting) = self.disconnecting.take() { - let mut position = if let Some(to_connector) = self.wire_in_progress_to_connector { to_connector } else { point }; - // Offset to drag from center of node - position = position - DVec2::new(24. * 3., 24.); - - // Offset to account for division rounding error - if position.x < 0. { - position.x = position.x - 1.; - } - if position.y < 0. { - position.y = position.y - 1.; - } - - let Some(input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else { - return; - }; - - let drag_start = DragStart { - start_x: point.x, - start_y: point.y, - round_x: 0, - round_y: 0, - }; - - self.drag_start = Some((drag_start, false)); - self.node_has_moved_in_drag = false; - self.update_node_graph_hints(responses); - - let node_id = NodeId::new(); - responses.add(NodeGraphMessage::CreateNodeFromContextMenu { - node_id: Some(node_id), - node_type: "Identity".to_string(), - xy: Some(((position.x / 24.) as i32, (position.y / 24.) as i32)), - }); - - responses.add(NodeGraphMessage::SetInput { - input_connector: InputConnector::node(node_id, 0), - input, - }); - - responses.add(NodeGraphMessage::CreateWire { - output_connector: OutputConnector::Node { node_id, output_index: 0 }, - input_connector: disconnecting, - }); - responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }); - // Update the frontend that the node is disconnected - responses.add(NodeGraphMessage::RunDocumentGraph); - responses.add(NodeGraphMessage::SendGraph); - } + // Not hovering over a node input or node output, create the value node if alt is pressed + else if ipp.keyboard.get(Key::Alt as usize) { + self.preview_on_mouse_up = None; + self.create_value_node(network_interface, point, breadcrumb_network_path, responses); + } else { + //TODO: Start creating wire } } else if let Some((drag_start, dragged)) = &mut self.drag_start { if drag_start.start_x != point.x || drag_start.start_y != point.y { @@ -1076,7 +1034,10 @@ impl<'a> MessageHandler> for NodeG } } - let mut graph_delta = IVec2::new(((point.x - drag_start.start_x) / 24.).round() as i32, ((point.y - drag_start.start_y) / 24.).round() as i32); + let mut graph_delta = IVec2::new( + ((point.x - drag_start.start_x) / GRID_SIZE as f64).round() as i32, + ((point.y - drag_start.start_y) / GRID_SIZE as f64).round() as i32, + ); let previous_round_x = drag_start.round_x; let previous_round_y = drag_start.round_y; @@ -1621,6 +1582,7 @@ impl<'a> MessageHandler> for NodeG let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p)); let mut nodes = Vec::new(); + for node_id in &self.frontend_nodes { let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { log::error!("Could not get bbox for node: {:?}", node_id); @@ -1637,6 +1599,17 @@ impl<'a> MessageHandler> for NodeG } } + // Always send nodes with errors + for error in &self.node_graph_errors { + let Some((id, path)) = error.node_path.split_last() else { + log::error!("Could not get node path in error: {:?}", error); + continue; + }; + if breadcrumb_network_path == path { + nodes.push(*id); + } + } + responses.add(FrontendMessage::UpdateVisibleNodes { nodes }); } NodeGraphMessage::SendGraph => { @@ -1675,7 +1648,7 @@ impl<'a> MessageHandler> for NodeG input, }); responses.add(PropertiesPanelMessage::Refresh); - if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) { + if network_interface.connected_to_output(&node_id, selection_network_path) { responses.add(NodeGraphMessage::RunDocumentGraph); } } @@ -1757,6 +1730,9 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendWires); } + NodeGraphMessage::SetReference { node_id, reference } => { + network_interface.set_reference(&node_id, breadcrumb_network_path, reference); + } NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => { if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { return; @@ -2496,6 +2472,69 @@ impl NodeGraphMessageHandler { } } + fn create_value_node(&mut self, network_interface: &mut NodeNetworkInterface, point: DVec2, breadcrumb_network_path: &[NodeId], responses: &mut VecDeque) { + let Some(disconnecting) = self.disconnecting.take() else { + log::error!("To connector must be initialized to create a value node"); + return; + }; + let Some(mut position) = self.wire_in_progress_to_connector.take() else { + log::error!("To connector must be initialized to create a value node"); + return; + }; + // Offset node insertion 3 grid spaces left and 1 grid space up so the center of the node is dragged + position = position - DVec2::new(GRID_SIZE as f64 * 3., GRID_SIZE as f64); + + // Offset to account for division rounding error and place the selected node to the top left of the input + if position.x < 0. { + position.x = position.x - 1.; + } + if position.y < 0. { + position.y = position.y - 1.; + } + + let Some(mut input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else { + return; + }; + + match &mut input { + NodeInput::Value { exposed, .. } => *exposed = false, + _ => return, + } + + let drag_start = DragStart { + start_x: point.x, + start_y: point.y, + round_x: 0, + round_y: 0, + }; + + self.drag_start = Some((drag_start, false)); + self.node_has_moved_in_drag = false; + self.update_node_graph_hints(responses); + + let node_id = NodeId::new(); + responses.add(NodeGraphMessage::CreateNodeFromContextMenu { + node_id: Some(node_id), + node_type: "Value".to_string(), + xy: Some(((position.x / GRID_SIZE as f64) as i32, (position.y / GRID_SIZE as f64) as i32)), + add_transaction: false, + }); + + responses.add(NodeGraphMessage::SetInput { + input_connector: InputConnector::node(node_id, 0), + input, + }); + + responses.add(NodeGraphMessage::CreateWire { + output_connector: OutputConnector::Node { node_id, output_index: 0 }, + input_connector: disconnecting, + }); + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }); + // Update the frontend that the node is disconnected + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SendGraph); + } + fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec { let mut added_wires = network_interface .node_graph_input_connectors(breadcrumb_network_path) diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 64fb50ada1..e6ffaa6d0b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -9,7 +9,7 @@ use choice::enum_choice; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use graph_craft::Type; -use graph_craft::document::value::TaggedValue; +use graph_craft::document::value::{TaggedValue, TaggedValueChoice}; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graphene_std::animation::RealTimeMode; use graphene_std::extract_xy::XY; @@ -89,7 +89,7 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec, display_decimal_places: Option, step: Option, + exposable: bool, context: &mut NodePropertiesContext, ) -> Result, Vec> { let (mut number_min, mut number_max, range) = number_options; @@ -144,7 +145,8 @@ pub(crate) fn property_from_type( let min = |x: f64| number_min.unwrap_or(x); let max = |x: f64| number_max.unwrap_or(x); - let default_info = ParameterWidgetsInfo::new(node_id, index, true, context); + let mut default_info = ParameterWidgetsInfo::new(node_id, index, true, context); + default_info.exposable = exposable; let mut extra_widgets = vec![]; let widgets = match ty { @@ -251,8 +253,8 @@ pub(crate) fn property_from_type( } } Type::Generic(_) => vec![TextLabel::new("Generic type (not supported)").widget_holder()].into(), - Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context), - Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, context), + Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context), + Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context), }; extra_widgets.push(widgets); @@ -1115,7 +1117,7 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default()); let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context); - parameter_info.exposeable = false; + parameter_info.exposable = false; let output_channel = enum_choice::().for_socket(parameter_info).property_row(); let document_node = match get_document_node(node_id, context) { @@ -1172,7 +1174,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp use graphene_std::raster::selective_color::*; let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context); - default_info.exposeable = false; + default_info.exposable = false; let colors = enum_choice::().for_socket(default_info).property_row(); let document_node = match get_document_node(node_id, context) { @@ -1526,7 +1528,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper let mut input_types = implementations .keys() .filter_map(|item| item.inputs.get(input_index)) - .filter(|ty| property_from_type(node_id, input_index, ty, number_options, unit_suffix, display_decimal_places, step, context).is_ok()) + .filter(|ty| property_from_type(node_id, input_index, ty, number_options, unit_suffix, display_decimal_places, step, true, context).is_ok()) .collect::>(); input_types.sort_by_key(|ty| ty.type_name()); let input_type = input_types.first().cloned(); @@ -1540,7 +1542,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper _ => context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path).0, }; - property_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, context).unwrap_or_else(|value| value) + property_from_type(node_id, input_index, &input_type, number_options, unit_suffix, display_decimal_places, step, true, context).unwrap_or_else(|value| value) }); layout.extend(row); @@ -1905,6 +1907,77 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> ] } +pub fn value_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let Some(document_node) = context.network_interface.document_node(&node_id, context.selection_network_path) else { + log::warn!("Value properties failed to be built because its document node is invalid."); + return vec![]; + }; + let Some(input) = document_node.inputs.get(0) else { + log::warn!("Secondary value input could not be found on value properties"); + return vec![]; + }; + let mut select_value_widgets = Vec::new(); + + select_value_widgets.push(TextLabel::new("Value type: ").tooltip("Select the type the value node should output").widget_holder()); + + let Some(input_value) = input.as_non_exposed_value() else { + log::error!("Primary value node input should be a hidden value input"); + return Vec::new(); + }; + + let input_type = input_value.ty(); + + let Some(choice) = TaggedValueChoice::from_tagged_value(input_value) else { + log::error!("Tagged value in value node should always have a choice. input: {:?}", input); + return Vec::new(); + }; + + // let committer = || {|_| { + // let messages = vec![ + // DocumentMessage::AddTransaction.into(), + // NodeGraphMessage::RunDocumentGraph.into(), + // ]; + // Message::Batched(messages.into_boxed_slice()).into() + // }; + let updater = || { + move |v: &TaggedValueChoice| { + let value = v.to_tagged_value(); + + let messages = vec![NodeGraphMessage::SetInputValue { node_id, input_index: 0, value }.into(), NodeGraphMessage::SendGraph.into()]; + + Message::Batched(messages.into_boxed_slice()).into() + } + }; + let value_dropdown = enum_choice::().dropdown_menu(choice, updater, || commit_value); + select_value_widgets.extend_from_slice(&[Separator::new(SeparatorType::Unrelated).widget_holder(), value_dropdown]); + + let mut type_widgets = match property_from_type(node_id, 0, &input_type, (None, None, None), None, None, None, false, context) { + Ok(type_widgets) => type_widgets, + Err(type_widgets) => type_widgets, + }; + + if type_widgets.len() <= 0 { + log::error!("Could not generate type widgets for value node"); + return Vec::new(); + } + + let LayoutGroup::Row { widgets: mut type_widgets } = type_widgets.remove(0) else { + log::error!("Could not get autogenerated widgets for value node"); + return Vec::new(); + }; + + if type_widgets.len() <= 2 { + log::error!("Could not generate type widgets for value node"); + return Vec::new(); + } + + //Remove the name and blank assist + type_widgets.remove(0); + type_widgets.remove(0); + + vec![LayoutGroup::Row { widgets: select_value_widgets }, LayoutGroup::Row { widgets: type_widgets }] +} + pub struct ParameterWidgetsInfo<'a> { document_node: Option<&'a DocumentNode>, node_id: NodeId, @@ -1913,7 +1986,7 @@ pub struct ParameterWidgetsInfo<'a> { description: String, input_type: FrontendGraphDataType, blank_assist: bool, - exposeable: bool, + exposable: bool, } impl<'a> ParameterWidgetsInfo<'a> { @@ -1930,7 +2003,7 @@ impl<'a> ParameterWidgetsInfo<'a> { description, input_type, blank_assist, - exposeable: true, + exposable: true, } } } @@ -1986,7 +2059,7 @@ pub mod choice { todo!() } - fn dropdown_menu(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetHolder + pub fn dropdown_menu(&self, current: E, updater_factory: impl Fn() -> U, committer_factory: impl Fn() -> C) -> WidgetHolder where U: Fn(&E) -> Message + 'static + Send + Sync, C: Fn(&()) -> Message + 'static + Send + Sync, diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 18fbfbd274..79b9dcea97 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4273,13 +4273,23 @@ impl NodeNetworkInterface { // Side effects match (&old_input, &new_input) { // If a node input is exposed or hidden reload the click targets and update the bounding box for all nodes - (NodeInput::Value { exposed: new_exposed, .. }, NodeInput::Value { exposed: old_exposed, .. }) => { + (NodeInput::Value { exposed: new_exposed, .. }, NodeInput::Value { exposed: old_exposed, tagged_value }) => { if let InputConnector::Node { node_id, .. } = input_connector { if new_exposed != old_exposed { self.unload_upstream_node_click_targets(vec![*node_id], network_path); self.unload_all_nodes_bounding_box(network_path); } } + // Update the name of the value node + if let InputConnector::Node { node_id, .. } = input_connector { + let Some(reference) = self.reference(node_id, network_path) else { + log::error!("Could not get reference for {:?}", node_id); + return; + }; + if reference.as_deref() == Some("Value") { + self.set_display_name(node_id, format!("{:?} Value", tagged_value.ty().nested_type()), network_path); + } + } } (_, NodeInput::Node { node_id: upstream_node_id, .. }) => { // Load structure if the change is to the document network and to the first or second diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 185aa22dd2..90bd39b942 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -13,28 +13,28 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode { } } -#[derive(Default, Debug, Clone, Copy)] -pub struct ValueNode(pub T); - -impl<'i, T: 'i, I> Node<'i, I> for ValueNode { - type Output = &'i T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - &self.0 - } -} - -impl ValueNode { - pub const fn new(value: T) -> ValueNode { - ValueNode(value) - } -} - -impl From for ValueNode { - fn from(value: T) -> Self { - ValueNode::new(value) - } -} +// #[derive(Default, Debug, Clone, Copy)] +// pub struct ValueNode(pub T); + +// impl<'i, T: 'i, I> Node<'i, I> for ValueNode { +// type Output = &'i T; +// #[inline(always)] +// fn eval(&'i self, _input: I) -> Self::Output { +// &self.0 +// } +// } + +// impl ValueNode { +// pub const fn new(value: T) -> ValueNode { +// ValueNode(value) +// } +// } + +// impl From for ValueNode { +// fn from(value: T) -> Self { +// ValueNode::new(value) +// } +// } #[derive(Default, Debug, Clone, Copy)] pub struct AsRefNode, U>(pub T, PhantomData); diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 8e24d5e359..a7b142226a 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -9,11 +9,13 @@ use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; use graphene_core::raster::Image; use graphene_core::raster_types::CPU; +use graphene_core::registry::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata}; use graphene_core::transform::ReferencePoint; use graphene_core::uuid::NodeId; use graphene_core::vector::style::Fill; -use graphene_core::{Color, MemoHash, Node, Type}; +use graphene_core::{AsU32, Color, MemoHash, Node, Type}; use graphene_svg_renderer::RenderMetadata; +use std::borrow::Cow; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; @@ -37,6 +39,65 @@ macro_rules! tagged_value { EditorApi(Arc) } + #[derive(Clone, Copy, Debug)] + #[repr(u32)] + pub enum TaggedValueChoice { + None, + $($identifier,)* + } + + impl TaggedValueChoice { + pub fn to_tagged_value(&self) -> TaggedValue { + match self { + TaggedValueChoice::None => TaggedValue::None, + $(TaggedValueChoice::$identifier => TaggedValue::$identifier(Default::default()),)* + } + } + pub fn from_tagged_value(value: &TaggedValue) -> Option { + match value { + TaggedValue::None => Some(TaggedValueChoice::None), + $( TaggedValue::$identifier(_) => Some(TaggedValueChoice::$identifier), )* + _ => None + } + } + } + + impl ChoiceTypeStatic for TaggedValueChoice { + const WIDGET_HINT: ChoiceWidgetHint = ChoiceWidgetHint::Dropdown; // or your preferred hint + + const DESCRIPTION: Option<&'static str> = Some("Select a value"); + + fn list() -> &'static [&'static [(Self, VariantMetadata)]] { + + const COUNT: usize = 0 $( + one!($identifier) )*; + // Define static array of (choice, metadata) tuples + static VALUES: [(TaggedValueChoice, VariantMetadata); 1 + COUNT] = [ + + (TaggedValueChoice::None, VariantMetadata { + name: Cow::Borrowed(stringify!(None)), + label: Cow::Borrowed(stringify!(None)), + docstring: None, + icon: None, + }), + $( + (TaggedValueChoice::$identifier, VariantMetadata { + name: Cow::Borrowed(stringify!($identifier)), + label: Cow::Borrowed(stringify!($identifier)), + docstring: None, + icon: None, + }), + )* + ]; + + // Static reference to the slice of VALUES + static LIST: [&'static [(TaggedValueChoice, VariantMetadata)]; 1] = [ + &VALUES, + ]; + + &LIST + } + } + // We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below) #[allow(clippy::derived_hash_with_manual_eq)] impl Hash for TaggedValue { @@ -156,6 +217,12 @@ macro_rules! tagged_value { }; } +macro_rules! one { + ($anything:tt) => { + 1 + }; +} + tagged_value! { // =============== // PRIMITIVE TYPES @@ -388,6 +455,12 @@ impl Display for TaggedValue { } } +impl AsU32 for TaggedValueChoice { + fn as_u32(&self) -> u32 { + *self as u32 + } +} + pub struct UpcastNode { value: MemoHash, } diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 1a38d65b35..27308a2352 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -46,3 +46,21 @@ pub fn input_node(n: SharedNodeContainer) -> DowncastBothNode<(), pub fn downcast_node(n: SharedNodeContainer) -> DowncastBothNode { DowncastBothNode::new(n) } + +// Same idea as the identity node, but with a hidden primary input in order to have an auto generated properties widget for the value +pub struct Value { + value: SharedNodeContainer, +} + +impl<'i> Node<'i, Any<'i>> for Value { + type Output = DynFuture<'i, Any<'i>>; + fn eval(&'i self, input: Any<'i>) -> Self::Output { + Box::pin(async move { self.value.eval(input).await }) + } +} + +impl Value { + pub const fn new(value: SharedNodeContainer) -> Self { + Value { value } + } +} diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 1dee5ed6da..bb5397a7f3 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -14,7 +14,7 @@ use graphene_std::Context; use graphene_std::GraphicElement; #[cfg(feature = "gpu")] use graphene_std::any::DowncastBothNode; -use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; +use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode, Value}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; #[cfg(feature = "gpu")] use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; @@ -114,6 +114,16 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => WgpuSurface]), #[cfg(feature = "gpu")] From 7ecd1d0054932165c184c3b371d8bef77f1c1935 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 6 Jul 2025 13:23:24 -0700 Subject: [PATCH 3/8] Migrate pass through and value node to identity implementation --- .../node_graph/document_node_definitions.rs | 16 ++++--- .../messages/portfolio/document_migration.rs | 25 ++--------- node-graph/gcore/src/ops.rs | 7 --- node-graph/gcore/src/value.rs | 44 +++++++++---------- node-graph/graph-craft/src/document.rs | 30 ++++++++++--- node-graph/graph-craft/src/proto.rs | 2 +- node-graph/gstd/src/any.rs | 9 ++-- node-graph/interpreted-executor/src/lib.rs | 6 +++ .../interpreted-executor/src/node_registry.rs | 6 +-- node-graph/interpreted-executor/src/util.rs | 6 +++ node-graph/preprocessor/src/lib.rs | 6 +++ 11 files changed, 86 insertions(+), 71 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 900bd3a6df..5bed5d0faa 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -85,11 +85,17 @@ fn static_nodes() -> Vec { let custom = vec![ // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { - identifier: "Identity", + identifier: "Pass Through", category: "General", node_template: NodeTemplate { document_node: DocumentNode { +<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), +||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) + implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), +======= + implementation: DocumentNodeImplementation::proto("graphene_std::any::IdentityNode"), +>>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) inputs: vec![NodeInput::value(TaggedValue::None, true)], ..Default::default() }, @@ -100,14 +106,14 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."), - properties: Some("identity_properties"), + properties: Some("pass_through_properties"), }, DocumentNodeDefinition { identifier: "Value", category: "General", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::any::ValueNode"), + implementation: DocumentNodeImplementation::proto("graphene_std::any::IdentityNode"), manual_composition: Some(generic!(T)), inputs: vec![NodeInput::value(TaggedValue::None, false)], ..Default::default() @@ -1918,8 +1924,8 @@ fn static_node_properties() -> NodeProperties { map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties)); map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties)); map.insert( - "identity_properties".to_string(), - Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")), + "pass_through_properties".to_string(), + Box::new(|_node_id, _context| node_properties::string_properties("The Pass Through node can be used to organize wires.")), ); map.insert( "monitor_properties".to_string(), diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index a55d63ff53..ba4a73a767 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -979,32 +979,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - - upgraded = true; + } else { + // Swap it back if we're not changing anything + let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); } } - - if !upgraded { - let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); - } } - // Add the "Depth" parameter to the "Instance Index" node - if reference == "Instance Index" && inputs_count == 0 { - let mut node_template = resolve_document_node_type(reference)?.default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, &mut node_template); - - let mut node_path = network_path.to_vec(); - node_path.push(*node_id); - - document.network_interface.add_import(TaggedValue::None, false, 0, "Primary", "", &node_path); - document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path); - } - - // ================================== - // PUT ALL MIGRATIONS ABOVE THIS LINE - // ================================== - // Ensure layers are positioned as stacks if they are upstream siblings of another layer document.network_interface.load_structure(); let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 0ef40a86a4..d5ffd57068 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,13 +1,6 @@ use crate::Node; use std::marker::PhantomData; -// TODO: Rename to "Passthrough" -/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes. -#[node_macro::node(skip_impl)] -fn identity<'i, T: 'i + Send>(value: T) -> T { - value -} - // Type // TODO: Document this #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 90bd39b942..185aa22dd2 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -13,28 +13,28 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode { } } -// #[derive(Default, Debug, Clone, Copy)] -// pub struct ValueNode(pub T); - -// impl<'i, T: 'i, I> Node<'i, I> for ValueNode { -// type Output = &'i T; -// #[inline(always)] -// fn eval(&'i self, _input: I) -> Self::Output { -// &self.0 -// } -// } - -// impl ValueNode { -// pub const fn new(value: T) -> ValueNode { -// ValueNode(value) -// } -// } - -// impl From for ValueNode { -// fn from(value: T) -> Self { -// ValueNode::new(value) -// } -// } +#[derive(Default, Debug, Clone, Copy)] +pub struct ValueNode(pub T); + +impl<'i, T: 'i, I> Node<'i, I> for ValueNode { + type Output = &'i T; + #[inline(always)] + fn eval(&'i self, _input: I) -> Self::Output { + &self.0 + } +} + +impl ValueNode { + pub const fn new(value: T) -> ValueNode { + ValueNode(value) + } +} + +impl From for ValueNode { + fn from(value: T) -> Self { + ValueNode::new(value) + } +} #[derive(Default, Debug, Clone, Copy)] pub struct AsRefNode, U>(pub T, PhantomData); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 08679ce417..ff13a83084 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -460,7 +460,7 @@ pub enum DocumentNodeImplementation { impl Default for DocumentNodeImplementation { fn default() -> Self { - Self::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")) + Self::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")) } } @@ -916,7 +916,7 @@ impl NodeNetwork { return; }; // If the node is hidden, replace it with an identity node - let identity_node = DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()); + let identity_node = DocumentNodeImplementation::ProtoNode("graphene_std::any::IdentityNode".into()); if !node.visible && node.implementation != identity_node { node.implementation = identity_node; @@ -1092,7 +1092,7 @@ impl NodeNetwork { fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> { let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone(); if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation { - if ident.name == "graphene_core::ops::IdentityNode" { + if ident.name == "graphene_std::any::IdentityNode" { assert_eq!(node.inputs.len(), 1, "Id node has more than one input"); if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] { let node_input_output_index = output_index; @@ -1139,13 +1139,13 @@ impl NodeNetwork { Ok(()) } - /// Strips out any [`graphene_core::ops::IdentityNode`]s that are unnecessary. + /// Strips out any [`graphene_std::any::IdentityNode`]s that are unnecessary. pub fn remove_redundant_id_nodes(&mut self) { let id_nodes = self .nodes .iter() .filter(|(_, node)| { - matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")) + matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")) && node.inputs.len() == 1 && matches!(node.inputs[0], NodeInput::Node { .. }) }) @@ -1333,7 +1333,7 @@ mod test { fn extract_node() { let id_node = DocumentNode { inputs: vec![], - implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), + implementation: DocumentNodeImplementation::ProtoNode("graphene_std::any::IdentityNode".into()), ..Default::default() }; // TODO: Extend test cases to test nested network @@ -1535,7 +1535,13 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], +<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), +======= + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), +>>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), @@ -1543,7 +1549,13 @@ mod test { NodeId(2), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 1)], +<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), +======= + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), +>>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), @@ -1570,7 +1582,13 @@ mod test { NodeId(2), DocumentNode { inputs: vec![result_node_input], +<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), +||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), +======= + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), +>>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 4330976609..dc325da75c 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -143,7 +143,7 @@ pub struct ProtoNode { impl Default for ProtoNode { fn default() -> Self { Self { - identifier: ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), + identifier: ProtoNodeIdentifier::new("graphene_std::any::IdentityNode"), construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), input: ProtoNodeInput::None, original_location: OriginalLocation::default(), diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 27308a2352..0cab682491 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -47,20 +47,19 @@ pub fn downcast_node(n: SharedNodeContainer) -> Do DowncastBothNode::new(n) } -// Same idea as the identity node, but with a hidden primary input in order to have an auto generated properties widget for the value -pub struct Value { +pub struct IdentityNode { value: SharedNodeContainer, } -impl<'i> Node<'i, Any<'i>> for Value { +impl<'i> Node<'i, Any<'i>> for IdentityNode { type Output = DynFuture<'i, Any<'i>>; fn eval(&'i self, input: Any<'i>) -> Self::Output { Box::pin(async move { self.value.eval(input).await }) } } -impl Value { +impl IdentityNode { pub const fn new(value: SharedNodeContainer) -> Self { - Value { value } + IdentityNode { value } } } diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 5c05ef62ba..2fdb942fad 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -20,7 +20,13 @@ mod tests { NodeId(0), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], +<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), +||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), +======= + implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), +>>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index bb5397a7f3..7dfee999f6 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -14,7 +14,7 @@ use graphene_std::Context; use graphene_std::GraphicElement; #[cfg(feature = "gpu")] use graphene_std::any::DowncastBothNode; -use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode, Value}; +use graphene_std::any::{ComposeTypeErased, DynAnyNode, IdentityNode, IntoTypeErasedNode}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; #[cfg(feature = "gpu")] use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; @@ -115,10 +115,10 @@ fn node_registry() -> HashMap>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], ..Default::default() }, diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index e0b4a01685..258b27c783 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -49,7 +49,13 @@ pub fn generate_node_substitutions() -> HashMap>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; From 54097ebcb5c77d1d7a1d1a8578199aad91caf0d4 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 14 Jul 2025 17:38:32 -0700 Subject: [PATCH 4/8] Code review, remove identity test --- .../node_graph/document_node_definitions.rs | 8 +---- .../document/node_graph/node_graph_message.rs | 4 --- .../node_graph/node_graph_message_handler.rs | 17 +++------ .../document/node_graph/node_properties.rs | 18 ++++------ .../utility_types/network_interface.rs | 2 +- .../messages/portfolio/document_migration.rs | 25 +++++++++++-- node-graph/gcore/src/ops.rs | 36 +++++++++++++------ node-graph/graph-craft/src/document.rs | 32 ++++------------- node-graph/graph-craft/src/proto.rs | 2 +- node-graph/gstd/src/any.rs | 17 --------- node-graph/interpreted-executor/src/lib.rs | 6 ---- .../interpreted-executor/src/node_registry.rs | 5 +-- node-graph/interpreted-executor/src/util.rs | 6 ---- node-graph/preprocessor/src/lib.rs | 6 ---- 14 files changed, 71 insertions(+), 113 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 5bed5d0faa..2ae8d8c748 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -89,13 +89,7 @@ fn static_nodes() -> Vec { category: "General", node_template: NodeTemplate { document_node: DocumentNode { -<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), -||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) - implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), -======= - implementation: DocumentNodeImplementation::proto("graphene_std::any::IdentityNode"), ->>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) inputs: vec![NodeInput::value(TaggedValue::None, true)], ..Default::default() }, @@ -113,7 +107,7 @@ fn static_nodes() -> Vec { category: "General", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_std::any::IdentityNode"), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), manual_composition: Some(generic!(T)), inputs: vec![NodeInput::value(TaggedValue::None, false)], ..Default::default() diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index ec76851949..bd6c77d467 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -152,10 +152,6 @@ pub enum NodeGraphMessage { node_id: NodeId, alias: String, }, - SetReference { - node_id: NodeId, - reference: Option, - }, SetToNodeOrLayer { node_id: NodeId, is_layer: bool, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 416b582934..2a899eed6c 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1582,7 +1582,6 @@ impl<'a> MessageHandler> for NodeG let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p)); let mut nodes = Vec::new(); - for node_id in &self.frontend_nodes { let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { log::error!("Could not get bbox for node: {:?}", node_id); @@ -1730,9 +1729,6 @@ impl<'a> MessageHandler> for NodeG responses.add(NodeGraphMessage::SendWires); } - NodeGraphMessage::SetReference { node_id, reference } => { - network_interface.set_reference(&node_id, breadcrumb_network_path, reference); - } NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => { if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { return; @@ -2484,21 +2480,16 @@ impl NodeGraphMessageHandler { // Offset node insertion 3 grid spaces left and 1 grid space up so the center of the node is dragged position = position - DVec2::new(GRID_SIZE as f64 * 3., GRID_SIZE as f64); - // Offset to account for division rounding error and place the selected node to the top left of the input - if position.x < 0. { - position.x = position.x - 1.; - } - if position.y < 0. { - position.y = position.y - 1.; - } - let Some(mut input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else { return; }; match &mut input { NodeInput::Value { exposed, .. } => *exposed = false, - _ => return, + _ => { + network_interface.set_input(&disconnecting, input, breadcrumb_network_path); + return; + } } let drag_start = DragStart { diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index e6ffaa6d0b..5116e65cb3 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -89,7 +89,7 @@ pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec return vec![]; }; let Some(input) = document_node.inputs.get(0) else { - log::warn!("Secondary value input could not be found on value properties"); + log::warn!("Value input could not be found in value properties"); return vec![]; }; let mut select_value_widgets = Vec::new(); @@ -1932,20 +1932,16 @@ pub fn value_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> return Vec::new(); }; - // let committer = || {|_| { - // let messages = vec![ - // DocumentMessage::AddTransaction.into(), - // NodeGraphMessage::RunDocumentGraph.into(), - // ]; - // Message::Batched(messages.into_boxed_slice()).into() - // }; let updater = || { move |v: &TaggedValueChoice| { let value = v.to_tagged_value(); let messages = vec![NodeGraphMessage::SetInputValue { node_id, input_index: 0, value }.into(), NodeGraphMessage::SendGraph.into()]; - Message::Batched(messages.into_boxed_slice()).into() + Message::Batched { + messages: messages.into_boxed_slice(), + } + .into() } }; let value_dropdown = enum_choice::().dropdown_menu(choice, updater, || commit_value); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 79b9dcea97..9a96be9f7f 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -503,7 +503,7 @@ impl NodeNetworkInterface { pub fn take_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option { let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get network in input_from_connector"); + log::error!("Could not get network in take_input"); return None; }; let input = match input_connector { diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index ba4a73a767..a55d63ff53 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -979,13 +979,32 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - } else { - // Swap it back if we're not changing anything - let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); + + upgraded = true; } } + + if !upgraded { + let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); + } } + // Add the "Depth" parameter to the "Instance Index" node + if reference == "Instance Index" && inputs_count == 0 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let mut node_path = network_path.to_vec(); + node_path.push(*node_id); + + document.network_interface.add_import(TaggedValue::None, false, 0, "Primary", "", &node_path); + document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path); + } + + // ================================== + // PUT ALL MIGRATIONS ABOVE THIS LINE + // ================================== + // Ensure layers are positioned as stacks if they are upstream siblings of another layer document.network_interface.load_structure(); let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index d5ffd57068..9af72901b9 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,6 +1,30 @@ -use crate::Node; +use crate::{ + Node, + registry::{Any, DynFuture, SharedNodeContainer}, +}; use std::marker::PhantomData; +pub struct IdentityNode { + value: SharedNodeContainer, +} + +impl<'i> Node<'i, Any<'i>> for IdentityNode { + type Output = DynFuture<'i, Any<'i>>; + fn eval(&'i self, input: Any<'i>) -> Self::Output { + Box::pin(async move { self.value.eval(input).await }) + } +} + +impl IdentityNode { + pub const fn new(value: SharedNodeContainer) -> Self { + IdentityNode { value } + } +} + +pub mod identity { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); +} + // Type // TODO: Document this #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] @@ -135,13 +159,3 @@ impl<'input, I: 'input + Convert<_O> + Sync + Send, _O: 'input> Node<'input, I> Box::pin(async move { input.convert() }) } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - pub fn identity_node() { - assert_eq!(identity(&4), &4); - } -} diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index ff13a83084..ea289d41a2 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -7,7 +7,7 @@ use glam::IVec2; use graphene_core::memo::MemoHashGuard; pub use graphene_core::uuid::NodeId; pub use graphene_core::uuid::generate_uuid; -use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type}; +use graphene_core::{Cow, MemoHash, ProtoNodeIdentifier, Type, ops}; use log::Metadata; use rustc_hash::FxHashMap; use std::collections::HashMap; @@ -460,7 +460,7 @@ pub enum DocumentNodeImplementation { impl Default for DocumentNodeImplementation { fn default() -> Self { - Self::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")) + Self::ProtoNode(ops::identity::IDENTIFIER) } } @@ -916,7 +916,7 @@ impl NodeNetwork { return; }; // If the node is hidden, replace it with an identity node - let identity_node = DocumentNodeImplementation::ProtoNode("graphene_std::any::IdentityNode".into()); + let identity_node = DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER); if !node.visible && node.implementation != identity_node { node.implementation = identity_node; @@ -1092,7 +1092,7 @@ impl NodeNetwork { fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> { let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone(); if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation { - if ident.name == "graphene_std::any::IdentityNode" { + if ident.name == ops::identity::IDENTIFIER.name { assert_eq!(node.inputs.len(), 1, "Id node has more than one input"); if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] { let node_input_output_index = output_index; @@ -1139,13 +1139,13 @@ impl NodeNetwork { Ok(()) } - /// Strips out any [`graphene_std::any::IdentityNode`]s that are unnecessary. + /// Strips out any [`graphene_std::ops::IdentityNode`]s that are unnecessary. pub fn remove_redundant_id_nodes(&mut self) { let id_nodes = self .nodes .iter() .filter(|(_, node)| { - matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")) + matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &ops::identity::IDENTIFIER) && node.inputs.len() == 1 && matches!(node.inputs[0], NodeInput::Node { .. }) }) @@ -1333,7 +1333,7 @@ mod test { fn extract_node() { let id_node = DocumentNode { inputs: vec![], - implementation: DocumentNodeImplementation::ProtoNode("graphene_std::any::IdentityNode".into()), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), ..Default::default() }; // TODO: Extend test cases to test nested network @@ -1535,13 +1535,7 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], -<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), -||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), -======= - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), ->>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), @@ -1549,13 +1543,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 1)], -<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), -||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), -======= - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), ->>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), @@ -1582,13 +1570,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![result_node_input], -<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), -||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), -======= - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), ->>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index dc325da75c..fc35f7717f 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -143,7 +143,7 @@ pub struct ProtoNode { impl Default for ProtoNode { fn default() -> Self { Self { - identifier: ProtoNodeIdentifier::new("graphene_std::any::IdentityNode"), + identifier: ops::identity::IDENTIFIER, construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), input: ProtoNodeInput::None, original_location: OriginalLocation::default(), diff --git a/node-graph/gstd/src/any.rs b/node-graph/gstd/src/any.rs index 0cab682491..1a38d65b35 100644 --- a/node-graph/gstd/src/any.rs +++ b/node-graph/gstd/src/any.rs @@ -46,20 +46,3 @@ pub fn input_node(n: SharedNodeContainer) -> DowncastBothNode<(), pub fn downcast_node(n: SharedNodeContainer) -> DowncastBothNode { DowncastBothNode::new(n) } - -pub struct IdentityNode { - value: SharedNodeContainer, -} - -impl<'i> Node<'i, Any<'i>> for IdentityNode { - type Output = DynFuture<'i, Any<'i>>; - fn eval(&'i self, input: Any<'i>) -> Self::Output { - Box::pin(async move { self.value.eval(input).await }) - } -} - -impl IdentityNode { - pub const fn new(value: SharedNodeContainer) -> Self { - IdentityNode { value } - } -} diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 2fdb942fad..5c05ef62ba 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -20,13 +20,7 @@ mod tests { NodeId(0), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], -<<<<<<< HEAD implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), -||||||| parent of 8e045313 (Migrate pass through and value node to identity implementation) - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), -======= - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::any::IdentityNode")), ->>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) ..Default::default() }, ), diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 7dfee999f6..3ecc4aa713 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -14,8 +14,9 @@ use graphene_std::Context; use graphene_std::GraphicElement; #[cfg(feature = "gpu")] use graphene_std::any::DowncastBothNode; -use graphene_std::any::{ComposeTypeErased, DynAnyNode, IdentityNode, IntoTypeErasedNode}; +use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; +use graphene_std::ops::IdentityNode; #[cfg(feature = "gpu")] use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; use node_registry_macros::{async_node, convert_node, into_node}; @@ -115,7 +116,7 @@ fn node_registry() -> HashMap>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], ..Default::default() }, diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 258b27c783..e0b4a01685 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -49,13 +49,7 @@ pub fn generate_node_substitutions() -> HashMap>>>>>> 8e045313 (Migrate pass through and value node to identity implementation) let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; From e0f317e67e750babbd6336977ccdf1b0c91f5762 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 15 Jul 2025 02:44:36 -0700 Subject: [PATCH 5/8] Old value node migrations --- .../node_graph/document_node_definitions.rs | 160 +++++++++++++++++- .../document/node_graph/node_properties.rs | 12 +- .../utility_types/network_interface.rs | 30 +++- .../messages/portfolio/document_migration.rs | 76 +++++---- .../floating-menus/NodeCatalog.svelte | 5 +- node-graph/gcore/src/structural.rs | 6 +- node-graph/gcore/src/value.rs | 30 ---- node-graph/gmath-nodes/src/lib.rs | 52 +----- node-graph/graph-craft/src/document/value.rs | 20 ++- 9 files changed, 256 insertions(+), 135 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 2ae8d8c748..a89de42564 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -104,7 +104,7 @@ fn static_nodes() -> Vec { }, DocumentNodeDefinition { identifier: "Value", - category: "General", + category: "Value", node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), @@ -113,12 +113,164 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_metadata: vec![("", "Value").into()], + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Construct any value using the dropdown menu."), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Number Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::F64(0.), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a number which can be set to any real number"), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Percentage Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::Percentage(0.), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a decimal value between 0 and 1."), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Number Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::U32(0), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a positive integer value."), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Bool Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::Bool(true), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a value which can be true or false"), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "String Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::Percentage(0.), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a string value which can be set to any plain text."), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Coordinate Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::DVec2(DVec2::new(0., 0.)), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a string value which can be set to any plain text."), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Color Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::OptionalColor(None), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a color value which may to set to any color, or no color"), + properties: Some("value_properties"), + }, + DocumentNodeDefinition { + identifier: "Gradient Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::OptionalColor(None), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], output_names: vec!["Out".to_string()], ..Default::default() }, }, - description: Cow::Borrowed("Returns the value stored in its input"), + description: Cow::Borrowed(" Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors."), properties: Some("value_properties"), }, // TODO: Auto-generate this from its proto node macro @@ -936,7 +1088,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed( - "Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.", + "Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate from Numbers\", which can have either or both its X and Y exposed as graph inputs.", ), properties: None, }, diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 5116e65cb3..c935f36242 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -256,7 +256,6 @@ pub(crate) fn property_from_type( Type::Fn(_, out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context), Type::Future(out) => return property_from_type(node_id, index, out, number_options, unit, display_decimal_places, step, exposable, context), }; - extra_widgets.push(widgets); Ok(extra_widgets) @@ -840,6 +839,17 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: .on_commit(commit_value) .widget_holder(), ]), + Some(&TaggedValue::Percentage(x)) => widgets.extend_from_slice(&[ + Separator::new(SeparatorType::Unrelated).widget_holder(), + number_props + .percentage() + .min(0.) + .max(100.) + .value(Some(x)) + .on_update(update_value(move |x: &NumberInput| TaggedValue::Percentage(x.value.unwrap()), node_id, index)) + .on_commit(commit_value) + .widget_holder(), + ]), Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[ Separator::new(SeparatorType::Unrelated).widget_holder(), number_props diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 9a96be9f7f..b2d277f984 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4087,20 +4087,32 @@ impl NodeNetworkInterface { self.unload_stack_dependents(network_path); } + pub fn set_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) { + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in replace_implementation"); + return; + }; + let Some(node) = network.nodes.get_mut(node_id) else { + log::error!("Could not get node in replace_implementation"); + return; + }; + node.implementation = implementation; + } + /// Replaces the implementation and corresponding metadata. pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) { let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in set_implementation"); + log::error!("Could not get nested network in replace_implementation"); return; }; let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in set_implementation"); + log::error!("Could not get node in replace_implementation"); return; }; let new_implementation = std::mem::take(&mut new_template.document_node.implementation); let _ = std::mem::replace(&mut node.implementation, new_implementation); let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { - log::error!("Could not get metadata in set_implementation"); + log::error!("Could not get metadata in replace_implementation"); return; }; let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.network_metadata); @@ -4110,17 +4122,17 @@ impl NodeNetworkInterface { /// Replaces the inputs and corresponding metadata. pub fn replace_inputs(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) -> Option> { let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in set_implementation"); + log::error!("Could not get nested network in replace_inputs"); return None; }; let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in set_implementation"); + log::error!("Could not get node in replace_inputs"); return None; }; let new_inputs = std::mem::take(&mut new_template.document_node.inputs); let old_inputs = std::mem::replace(&mut node.inputs, new_inputs); let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { - log::error!("Could not get metadata in set_implementation"); + log::error!("Could not get metadata in replace_inputs"); return None; }; let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.input_metadata); @@ -4177,13 +4189,13 @@ impl NodeNetworkInterface { } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn set_manual_compostion(&mut self, node_id: &NodeId, network_path: &[NodeId], manual_composition: Option) { + pub fn set_manual_composition(&mut self, node_id: &NodeId, network_path: &[NodeId], manual_composition: Option) { let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in set_implementation"); + log::error!("Could not get nested network in set_manual_composition"); return; }; let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in set_implementation"); + log::error!("Could not get node in set_manual_composition"); return; }; node.manual_composition = manual_composition; diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index a55d63ff53..a6dc4f9acf 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -173,42 +173,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ node: graphene_std::math_nodes::logical_not::IDENTIFIER, aliases: &["graphene_core::ops::LogicalNotNode", "graphene_core::ops::LogicOrNode", "graphene_core::logic::LogicOrNode"], }, - NodeReplacement { - node: graphene_std::math_nodes::bool_value::IDENTIFIER, - aliases: &["graphene_core::ops::BoolValueNode"], - }, - NodeReplacement { - node: graphene_std::math_nodes::number_value::IDENTIFIER, - aliases: &["graphene_core::ops::NumberValueNode"], - }, - NodeReplacement { - node: graphene_std::math_nodes::percentage_value::IDENTIFIER, - aliases: &["graphene_core::ops::PercentageValueNode"], - }, - NodeReplacement { - node: graphene_std::math_nodes::coordinate_value::IDENTIFIER, - aliases: &[ - "graphene_core::ops::CoordinateValueNode", - "graphene_core::ops::ConstructVector2", - "graphene_core::ops::Vector2ValueNode", - ], - }, - NodeReplacement { - node: graphene_std::math_nodes::color_value::IDENTIFIER, - aliases: &["graphene_core::ops::ColorValueNode"], - }, - NodeReplacement { - node: graphene_std::math_nodes::gradient_value::IDENTIFIER, - aliases: &["graphene_core::ops::GradientValueNode"], - }, NodeReplacement { node: graphene_std::math_nodes::sample_gradient::IDENTIFIER, aliases: &["graphene_core::ops::SampleGradientNode"], }, - NodeReplacement { - node: graphene_std::math_nodes::string_value::IDENTIFIER, - aliases: &["graphene_core::ops::StringValueNode"], - }, NodeReplacement { node: graphene_std::math_nodes::dot_product::IDENTIFIER, aliases: &["graphene_core::ops::DotProductNode"], @@ -510,7 +478,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ let mut default_template = NodeTemplate::default(); default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone()); document.network_interface.replace_implementation(node_id, &network_path, &mut default_template); - document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); + document.network_interface.set_manual_composition(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); } } } @@ -539,7 +507,45 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { document .network_interface - .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); + .set_manual_composition(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); + } + + // Update old value nodes after https://github.com/GraphiteEditor/Graphite/pull/2822 + if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &node.implementation { + let value_node_names = [ + "graphene_math_nodes::BoolValueNode", + "graphene_math_nodes::ColorValueNode", + "graphene_math_nodes::PercentageValueNode", + "graphene_math_nodes::NumberValueNode", + "graphene_math_nodes::StringValueNode", + "graphene_math_nodes::GradientValueNode", + ]; + if value_node_names.iter().any(|&s| s == name) { + let mut template = resolve_document_node_type("Value")?.default_node_template(); + document.network_interface.replace_implementation(node_id, &network_path, &mut template); + let mut old_inputs = document.network_interface.replace_inputs(node_id, &network_path, &mut template)?; + document.network_interface.set_reference(node_id, network_path, Some("Value".to_string())); + if name == "graphene_math_nodes::PercentageValueNode" { + if let NodeInput::Value { tagged_value, .. } = &old_inputs[1] { + if let TaggedValue::F64(value) = &**tagged_value { + old_inputs[1] = NodeInput::value(TaggedValue::Percentage(*value), false); + } + } + } + // Only migrate value inputs, if its a wire the value is unknown. + if let NodeInput::Value { tagged_value, .. } = old_inputs[1].clone() { + document + .network_interface + .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(tagged_value.into_inner(), false), network_path); + } + } else if name == "graphene_math_nodes::CoordinateValueNode" { + document.network_interface.set_implementation( + node_id, + &network_path, + DocumentNodeImplementation::ProtoNode(graphene_std::math_nodes::coordinate_from_numbers::IDENTIFIER), + ); + document.network_interface.set_reference(node_id, network_path, Some("Coordinate From Numbers".to_string())) + } } // Only nodes that have not been modified and still refer to a definition can be updated @@ -1007,7 +1013,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], // Ensure layers are positioned as stacks if they are upstream siblings of another layer document.network_interface.load_structure(); - let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); + let all_layers: Vec = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); for layer in all_layers { let (downstream_node, input_index) = document .network_interface diff --git a/frontend/src/components/floating-menus/NodeCatalog.svelte b/frontend/src/components/floating-menus/NodeCatalog.svelte index a0f2cbe241..65b867004c 100644 --- a/frontend/src/components/floating-menus/NodeCatalog.svelte +++ b/frontend/src/components/floating-menus/NodeCatalog.svelte @@ -54,7 +54,10 @@ // Quick and dirty hack to alias "Layer" to "Merge" in the search const layerAliasMatch = node.name === "Merge" && "layer".includes(term); - return nameMatch || categoryMatch || layerAliasMatch; + // Alias "Identity" to "Pass Through" + const identityAliasMatch = node.name === "Pass Through" && "identity".includes(term); + + return nameMatch || categoryMatch || layerAliasMatch || identityAliasMatch; }); } diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index b6488c573c..cdba7eade4 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -128,11 +128,11 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode { mod test { use super::*; use crate::generic::FnNode; - use crate::value::ValueNode; + use crate::value::ClonedNode; #[test] fn compose() { - let value = ValueNode::new(4u32); + let value = ClonedNode::new(4u32); let compose = value.then(FnNode::new(|x| x)); assert_eq!(compose.eval(()), &4u32); let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>; @@ -141,7 +141,7 @@ mod test { #[test] fn test_ref_eval() { - let value = ValueNode::new(5); + let value = ClonedNode::new(5); assert_eq!(value.eval(()), &5); let id = FnNode::new(|x| x); diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 185aa22dd2..9f38a2c92f 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -13,29 +13,6 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode { } } -#[derive(Default, Debug, Clone, Copy)] -pub struct ValueNode(pub T); - -impl<'i, T: 'i, I> Node<'i, I> for ValueNode { - type Output = &'i T; - #[inline(always)] - fn eval(&'i self, _input: I) -> Self::Output { - &self.0 - } -} - -impl ValueNode { - pub const fn new(value: T) -> ValueNode { - ValueNode(value) - } -} - -impl From for ValueNode { - fn from(value: T) -> Self { - ValueNode::new(value) - } -} - #[derive(Default, Debug, Clone, Copy)] pub struct AsRefNode, U>(pub T, PhantomData); @@ -192,13 +169,6 @@ mod test { assert_eq!(node.eval(()), 5); } #[test] - fn test_value_node() { - let node = ValueNode::new(5); - assert_eq!(node.eval(()), &5); - let type_erased = &node as &dyn for<'a> Node<'a, (), Output = &'a i32>; - assert_eq!(type_erased.eval(()), &5); - } - #[test] fn test_default_node() { let node = DefaultNode::::new(); assert_eq!(node.eval(42), 0); diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index f79bc88684..730c0f3211 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -1,5 +1,6 @@ use glam::{DAffine2, DVec2}; use graphene_core::gradient::GradientStops; +use graphene_core::registry::types::Fraction; use graphene_core::registry::types::{Fraction, Percentage, PixelSize, TextArea}; use graphene_core::transform::Footprint; use graphene_core::{Color, Ctx, num_traits}; @@ -633,35 +634,7 @@ fn logical_not( !input } -/// Constructs a bool value which may be set to true or false. -#[node_macro::node(category("Value"))] -fn bool_value(_: impl Ctx, _primary: (), #[name("Bool")] bool_value: bool) -> bool { - bool_value -} - -/// Constructs a number value which may be set to any real number. -#[node_macro::node(category("Value"))] -fn number_value(_: impl Ctx, _primary: (), number: f64) -> f64 { - number -} - -/// Constructs a number value which may be set to any value from 0% to 100% by dragging the slider. -#[node_macro::node(category("Value"))] -fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 { - percentage -} - /// Constructs a two-dimensional vector value which may be set to any XY coordinate. -#[node_macro::node(category("Value"))] -fn coordinate_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 { - DVec2::new(x, y) -} - -/// Constructs a color value which may be set to any color, or no color. -#[node_macro::node(category("Value"))] -fn color_value(_: impl Ctx, _primary: (), #[default(Color::BLACK)] color: Option) -> Option { - color -} /// Gets the color at the specified position along the gradient, given a position from 0 (left) to 1 (right). #[node_macro::node(category("Color"))] @@ -670,26 +643,9 @@ fn sample_gradient(_: impl Ctx, _primary: (), gradient: GradientStops, position: gradient.evaluate(position) } -/// Constructs a gradient value which may be set to any sequence of color stops to represent the transition between colors. -#[node_macro::node(category("Value"))] -fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> GradientStops { - gradient -} - -/// Constructs a string value which may be set to any plain text. -#[node_macro::node(category("Value"))] -fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String { - string -} - -/// Constructs a footprint value which may be set to any transformation of a unit square describing a render area, and a render resolution at least 1x1 integer pixels. -#[node_macro::node(category("Value"))] -fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100., 100.)] resolution: PixelSize) -> Footprint { - Footprint { - transform, - resolution: resolution.max(DVec2::ONE).as_uvec2(), - ..Default::default() - } +#[node_macro::node(category("Math: Vector"))] +fn coordinate_from_numbers(_: impl Ctx, _primary: (), #[expose] x: f64, #[expose] y: f64) -> DVec2 { + DVec2::new(x, y) } #[node_macro::node(category("Math: Vector"))] diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index a7b142226a..3bcecc8aed 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -9,6 +9,7 @@ use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; use graphene_core::raster::Image; use graphene_core::raster_types::CPU; +use graphene_core::registry::types::Percentage; use graphene_core::registry::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata}; use graphene_core::transform::ReferencePoint; use graphene_core::uuid::NodeId; @@ -21,7 +22,6 @@ use std::hash::Hash; use std::marker::PhantomData; use std::str::FromStr; pub use std::sync::Arc; - pub struct TaggedValueTypeError; /// Macro to generate the tagged value enum. @@ -32,6 +32,7 @@ macro_rules! tagged_value { #[allow(clippy::large_enum_variant)] // TODO(TrueDoctor): Properly solve this disparity between the size of the largest and next largest variants pub enum TaggedValue { None, + Percentage(Percentage), $( $(#[$meta] ) *$identifier( $ty ), )* RenderOutput(RenderOutput), SurfaceFrame(SurfaceFrame), @@ -43,6 +44,7 @@ macro_rules! tagged_value { #[repr(u32)] pub enum TaggedValueChoice { None, + Percentage, $($identifier,)* } @@ -50,12 +52,14 @@ macro_rules! tagged_value { pub fn to_tagged_value(&self) -> TaggedValue { match self { TaggedValueChoice::None => TaggedValue::None, + TaggedValueChoice::Percentage => TaggedValue::Percentage(0.), $(TaggedValueChoice::$identifier => TaggedValue::$identifier(Default::default()),)* } } pub fn from_tagged_value(value: &TaggedValue) -> Option { match value { TaggedValue::None => Some(TaggedValueChoice::None), + TaggedValue::Percentage(_) => Some(TaggedValueChoice::Percentage), $( TaggedValue::$identifier(_) => Some(TaggedValueChoice::$identifier), )* _ => None } @@ -71,7 +75,7 @@ macro_rules! tagged_value { const COUNT: usize = 0 $( + one!($identifier) )*; // Define static array of (choice, metadata) tuples - static VALUES: [(TaggedValueChoice, VariantMetadata); 1 + COUNT] = [ + static VALUES: [(TaggedValueChoice, VariantMetadata); 2 + COUNT] = [ (TaggedValueChoice::None, VariantMetadata { name: Cow::Borrowed(stringify!(None)), @@ -79,6 +83,12 @@ macro_rules! tagged_value { docstring: None, icon: None, }), + (TaggedValueChoice::Percentage, VariantMetadata { + name: Cow::Borrowed(stringify!(Percentage)), + label: Cow::Borrowed(stringify!(Percentage)), + docstring: None, + icon: None, + }), $( (TaggedValueChoice::$identifier, VariantMetadata { name: Cow::Borrowed(stringify!($identifier)), @@ -105,6 +115,7 @@ macro_rules! tagged_value { core::mem::discriminant(self).hash(state); match self { Self::None => {} + Self::Percentage(x) => {x.hash(state)}, $( Self::$identifier(x) => {x.hash(state)}),* Self::RenderOutput(x) => x.hash(state), Self::SurfaceFrame(x) => x.hash(state), @@ -117,6 +128,7 @@ macro_rules! tagged_value { pub fn to_dynany(self) -> DAny<'a> { match self { Self::None => Box::new(()), + Self::Percentage(x) => Box::new(x), $( Self::$identifier(x) => Box::new(x), )* Self::RenderOutput(x) => Box::new(x), Self::SurfaceFrame(x) => Box::new(x), @@ -127,6 +139,7 @@ macro_rules! tagged_value { pub fn to_any(self) -> Arc { match self { Self::None => Arc::new(()), + Self::Percentage(x) => Arc::new(x), $( Self::$identifier(x) => Arc::new(x), )* Self::RenderOutput(x) => Arc::new(x), Self::SurfaceFrame(x) => Arc::new(x), @@ -137,6 +150,7 @@ macro_rules! tagged_value { pub fn ty(&self) -> Type { match self { Self::None => concrete!(()), + Self::Percentage(_) => concrete!(Percentage), $( Self::$identifier(_) => concrete!($ty), )* Self::RenderOutput(_) => concrete!(RenderOutput), Self::SurfaceFrame(_) => concrete!(SurfaceFrame), @@ -153,8 +167,6 @@ macro_rules! tagged_value { $( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )* x if x == TypeId::of::() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())), x if x == TypeId::of::() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())), - - _ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))), } } From faf8dedb1bb6cab79a095c9f6420a965aa00023b Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 15 Jul 2025 03:02:39 -0700 Subject: [PATCH 6/8] Fix tests --- node-graph/gcore/src/structural.rs | 6 +++--- node-graph/gcore/src/value.rs | 23 +++++++++++++++++++++++ node-graph/graph-craft/src/proto.rs | 12 ++++++------ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/node-graph/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index cdba7eade4..cbdd8beb17 100644 --- a/node-graph/gcore/src/structural.rs +++ b/node-graph/gcore/src/structural.rs @@ -128,11 +128,11 @@ impl<'i, Root: Node<'i, I>, I: 'i + From<()>> ConsNode { mod test { use super::*; use crate::generic::FnNode; - use crate::value::ClonedNode; + use crate::value::ValueRefNode; #[test] fn compose() { - let value = ClonedNode::new(4u32); + let value = ValueRefNode::new(4u32); let compose = value.then(FnNode::new(|x| x)); assert_eq!(compose.eval(()), &4u32); let type_erased = &compose as &dyn Node<'_, (), Output = &'_ u32>; @@ -141,7 +141,7 @@ mod test { #[test] fn test_ref_eval() { - let value = ClonedNode::new(5); + let value = ValueRefNode::new(5); assert_eq!(value.eval(()), &5); let id = FnNode::new(|x| x); diff --git a/node-graph/gcore/src/value.rs b/node-graph/gcore/src/value.rs index 9f38a2c92f..453fd1eaae 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -13,6 +13,29 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode { } } +#[derive(Default, Debug, Clone, Copy)] +pub struct ValueRefNode(pub T); + +impl<'i, T: 'i, I> Node<'i, I> for ValueRefNode { + type Output = &'i T; + #[inline(always)] + fn eval(&'i self, _input: I) -> Self::Output { + &self.0 + } +} + +impl ValueRefNode { + pub const fn new(value: T) -> ValueRefNode { + ValueRefNode(value) + } +} + +impl From for ValueRefNode { + fn from(value: T) -> Self { + ValueRefNode::new(value) + } +} + #[derive(Default, Debug, Clone, Copy)] pub struct AsRefNode, U>(pub T, PhantomData); diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index fc35f7717f..7a5e55ec67 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -950,12 +950,12 @@ mod test { assert_eq!( ids, vec![ - NodeId(16997244687192517417), - NodeId(12226224850522777131), - NodeId(9162113827627229771), - NodeId(12793582657066318419), - NodeId(16945623684036608820), - NodeId(2640415155091892458) + NodeId(12524483273268761808), + NodeId(14274752063205226537), + NodeId(7177002187999680489), + NodeId(17211043138366459510), + NodeId(6979825042154430163), + NodeId(2888425170017482846) ] ); } From 84b3c90dc890b4de1d035976c91e96921586e225 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 15 Jul 2025 12:14:12 -0700 Subject: [PATCH 7/8] rename passthrough --- .../document/node_graph/document_node_definitions.rs | 4 ++-- frontend/src/components/floating-menus/NodeCatalog.svelte | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index a89de42564..a3d0f1965c 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -85,7 +85,7 @@ fn static_nodes() -> Vec { let custom = vec![ // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { - identifier: "Pass Through", + identifier: "Passthrough", category: "General", node_template: NodeTemplate { document_node: DocumentNode { @@ -2071,7 +2071,7 @@ fn static_node_properties() -> NodeProperties { map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties)); map.insert( "pass_through_properties".to_string(), - Box::new(|_node_id, _context| node_properties::string_properties("The Pass Through node can be used to organize wires.")), + Box::new(|_node_id, _context| node_properties::string_properties("The Passthrough node can be used to organize wires.")), ); map.insert( "monitor_properties".to_string(), diff --git a/frontend/src/components/floating-menus/NodeCatalog.svelte b/frontend/src/components/floating-menus/NodeCatalog.svelte index 65b867004c..44f91aac8e 100644 --- a/frontend/src/components/floating-menus/NodeCatalog.svelte +++ b/frontend/src/components/floating-menus/NodeCatalog.svelte @@ -54,8 +54,8 @@ // Quick and dirty hack to alias "Layer" to "Merge" in the search const layerAliasMatch = node.name === "Merge" && "layer".includes(term); - // Alias "Identity" to "Pass Through" - const identityAliasMatch = node.name === "Pass Through" && "identity".includes(term); + // Alias "Identity" to "Passthrough" + const identityAliasMatch = node.name === "Passthrough" && "identity".includes(term); return nameMatch || categoryMatch || layerAliasMatch || identityAliasMatch; }); From a2a6b3e12fe018b7395988116d73998961dc8262 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 26 Jul 2025 14:55:40 -0700 Subject: [PATCH 8/8] Footprint widget --- .../node_graph/document_node_definitions.rs | 33 +++++++++++++++---- .../document/node_graph/node_properties.rs | 28 +++++++++------- node-graph/gmath-nodes/src/lib.rs | 2 -- node-graph/graph-craft/src/document/value.rs | 15 ++++----- 4 files changed, 49 insertions(+), 29 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index a3d0f1965c..bd1488ec13 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -122,7 +122,7 @@ fn static_nodes() -> Vec { properties: Some("value_properties"), }, DocumentNodeDefinition { - identifier: "Number Value", + identifier: "Real Number Value", category: "Value", node_template: NodeTemplate { document_node: DocumentNode { @@ -141,13 +141,13 @@ fn static_nodes() -> Vec { properties: Some("value_properties"), }, DocumentNodeDefinition { - identifier: "Percentage Value", + identifier: "Whole Number Value", category: "Value", node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), manual_composition: Some(generic!(T)), - inputs: vec![NodeInput::value(TaggedValue::Percentage(0.), false)], + inputs: vec![NodeInput::value(TaggedValue::U32(0), false)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -156,17 +156,17 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - description: Cow::Borrowed("Constructs a decimal value between 0 and 1."), + description: Cow::Borrowed("Constructs a positive integer value."), properties: Some("value_properties"), }, DocumentNodeDefinition { - identifier: "Number Value", + identifier: "Percentage Value", category: "Value", node_template: NodeTemplate { document_node: DocumentNode { implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), manual_composition: Some(generic!(T)), - inputs: vec![NodeInput::value(TaggedValue::U32(0), false)], + inputs: vec![NodeInput::value(TaggedValue::Percentage(0.), false)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { @@ -175,7 +175,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - description: Cow::Borrowed("Constructs a positive integer value."), + description: Cow::Borrowed("Constructs a decimal value between 0 and 1."), properties: Some("value_properties"), }, DocumentNodeDefinition { @@ -235,6 +235,25 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Constructs a string value which can be set to any plain text."), properties: Some("value_properties"), }, + DocumentNodeDefinition { + identifier: "Footprint Value", + category: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + manual_composition: Some(generic!(T)), + inputs: vec![NodeInput::value(TaggedValue::Footprint(Footprint::default()), false)], + ..Default::default() + }, + persistent_node_metadata: DocumentNodePersistentMetadata { + input_metadata: vec![("Value", "").into()], + output_names: vec!["Out".to_string()], + ..Default::default() + }, + }, + description: Cow::Borrowed("Constructs a string value which can be set to any plain text."), + properties: Some("value_properties"), + }, DocumentNodeDefinition { identifier: "Color Value", category: "Value", diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index c935f36242..29f1616be0 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1967,21 +1967,25 @@ pub fn value_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> return Vec::new(); } - let LayoutGroup::Row { widgets: mut type_widgets } = type_widgets.remove(0) else { - log::error!("Could not get autogenerated widgets for value node"); - return Vec::new(); - }; + for row in &mut type_widgets { + let LayoutGroup::Row { widgets: type_widgets_first_row } = row else { + log::error!("Could not get autogenerated widgets for value node"); + continue; + }; - if type_widgets.len() <= 2 { - log::error!("Could not generate type widgets for value node"); - return Vec::new(); - } + if type_widgets_first_row.len() <= 2 { + log::error!("Could not generate type widgets for value node"); + continue; + } - //Remove the name and blank assist - type_widgets.remove(0); - type_widgets.remove(0); + //Remove the name and blank assist + type_widgets_first_row.remove(0); + type_widgets_first_row.remove(0); + } - vec![LayoutGroup::Row { widgets: select_value_widgets }, LayoutGroup::Row { widgets: type_widgets }] + let mut full_value_widgets = vec![LayoutGroup::Row { widgets: select_value_widgets }]; + full_value_widgets.extend(type_widgets); + full_value_widgets } pub struct ParameterWidgetsInfo<'a> { diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index 730c0f3211..9fbc7f846c 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -1,8 +1,6 @@ use glam::{DAffine2, DVec2}; use graphene_core::gradient::GradientStops; use graphene_core::registry::types::Fraction; -use graphene_core::registry::types::{Fraction, Percentage, PixelSize, TextArea}; -use graphene_core::transform::Footprint; use graphene_core::{Color, Ctx, num_traits}; use log::warn; use math_parser::ast; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 3bcecc8aed..0aaee83665 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -7,16 +7,15 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use graphene_application_io::SurfaceFrame; use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; +use graphene_core::choice_type::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata}; use graphene_core::raster::Image; use graphene_core::raster_types::CPU; use graphene_core::registry::types::Percentage; -use graphene_core::registry::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata}; use graphene_core::transform::ReferencePoint; use graphene_core::uuid::NodeId; use graphene_core::vector::style::Fill; use graphene_core::{AsU32, Color, MemoHash, Node, Type}; use graphene_svg_renderer::RenderMetadata; -use std::borrow::Cow; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; @@ -78,21 +77,21 @@ macro_rules! tagged_value { static VALUES: [(TaggedValueChoice, VariantMetadata); 2 + COUNT] = [ (TaggedValueChoice::None, VariantMetadata { - name: Cow::Borrowed(stringify!(None)), - label: Cow::Borrowed(stringify!(None)), + name: stringify!(None), + label: stringify!(None), docstring: None, icon: None, }), (TaggedValueChoice::Percentage, VariantMetadata { - name: Cow::Borrowed(stringify!(Percentage)), - label: Cow::Borrowed(stringify!(Percentage)), + name: stringify!(Percentage), + label: stringify!(Percentage), docstring: None, icon: None, }), $( (TaggedValueChoice::$identifier, VariantMetadata { - name: Cow::Borrowed(stringify!($identifier)), - label: Cow::Borrowed(stringify!($identifier)), + name: stringify!($identifier), + label: stringify!($identifier), docstring: None, icon: None, }),