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 67f7704530..7512d23676 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: "Identity", + identifier: "Passthrough", category: "General", node_template: NodeTemplate { document_node: DocumentNode { @@ -100,7 +100,178 @@ 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: "Value", + node_template: NodeTemplate { + document_node: DocumentNode { + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), + 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("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(" 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 DocumentNodeDefinition { @@ -915,7 +1086,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, }, @@ -1816,13 +1987,14 @@ 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 Passthrough node can be used to organize wires.")), ); map.insert( "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_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index d7391227c9..a8addb3161 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 @@ -499,7 +499,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 { @@ -604,7 +607,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); @@ -939,6 +942,24 @@ impl<'a> MessageHandler> for NodeG }; responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: Some(wire_path) }); } + } + // 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 { + 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, 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 { *dragged = true; @@ -958,7 +979,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; @@ -1356,6 +1380,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 => { @@ -1394,7 +1429,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); } } @@ -2157,6 +2192,64 @@ 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); + + let Some(mut input) = network_interface.take_input(&disconnecting, breadcrumb_network_path) else { + return; + }; + + match &mut input { + NodeInput::Value { exposed, .. } => *exposed = false, + _ => { + network_interface.set_input(&disconnecting, input, breadcrumb_network_path); + 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 c69574b9a2..bd7a46d3b2 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, IVec2, UVec2}; 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 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 { @@ -252,10 +254,9 @@ 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); Ok(extra_widgets) @@ -767,6 +768,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 @@ -1044,7 +1056,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) { @@ -1101,7 +1113,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) { @@ -1455,7 +1467,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(); @@ -1469,7 +1481,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); @@ -1834,6 +1846,73 @@ 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!("Value input could not be found in 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 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: 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, @@ -1842,7 +1921,7 @@ pub struct ParameterWidgetsInfo<'a> { description: String, input_type: FrontendGraphDataType, blank_assist: bool, - exposeable: bool, + exposable: bool, } impl<'a> ParameterWidgetsInfo<'a> { @@ -1859,7 +1938,7 @@ impl<'a> ParameterWidgetsInfo<'a> { description, input_type, blank_assist, - exposeable: true, + exposable: true, } } } @@ -1915,7 +1994,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 404d3486a0..bebe07d23e 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 take_input"); + 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 { @@ -4051,20 +4069,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); @@ -4074,17 +4104,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); @@ -4141,13 +4171,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; @@ -4237,13 +4267,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/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 96213b4ee8..efb2ffbf34 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"], @@ -499,7 +467,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()))); } } } @@ -528,7 +496,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 @@ -987,7 +993,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..44f91aac8e 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 "Passthrough" + const identityAliasMatch = node.name === "Passthrough" && "identity".includes(term); + + return nameMatch || categoryMatch || layerAliasMatch || identityAliasMatch; }); } diff --git a/node-graph/gcore/src/ops.rs b/node-graph/gcore/src/ops.rs index 0ef40a86a4..9af72901b9 100644 --- a/node-graph/gcore/src/ops.rs +++ b/node-graph/gcore/src/ops.rs @@ -1,11 +1,28 @@ -use crate::Node; +use crate::{ + Node, + registry::{Any, DynFuture, SharedNodeContainer}, +}; 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 +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 @@ -142,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/gcore/src/structural.rs b/node-graph/gcore/src/structural.rs index b6488c573c..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::ValueNode; + use crate::value::ValueRefNode; #[test] fn compose() { - let value = ValueNode::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 = ValueNode::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 3c3ff94c74..c1c9a0dd37 100644 --- a/node-graph/gcore/src/value.rs +++ b/node-graph/gcore/src/value.rs @@ -14,9 +14,9 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode { } #[derive(Default, Debug, Clone, Copy)] -pub struct ValueNode(pub T); +pub struct ValueRefNode(pub T); -impl<'i, T: 'i, I> Node<'i, I> for ValueNode { +impl<'i, T: 'i, I> Node<'i, I> for ValueRefNode { type Output = &'i T; #[inline(always)] fn eval(&'i self, _input: I) -> Self::Output { @@ -24,15 +24,15 @@ impl<'i, T: 'i, I> Node<'i, I> for ValueNode { } } -impl ValueNode { - pub const fn new(value: T) -> ValueNode { - ValueNode(value) +impl ValueRefNode { + pub const fn new(value: T) -> ValueRefNode { + ValueRefNode(value) } } -impl From for ValueNode { +impl From for ValueRefNode { fn from(value: T) -> Self { - ValueNode::new(value) + ValueRefNode::new(value) } } @@ -193,13 +193,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 34bf434b2c..7ac857e4d9 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -1,6 +1,6 @@ use glam::DVec2; use graphene_core::gradient::GradientStops; -use graphene_core::registry::types::{Fraction, Percentage, TextArea}; +use graphene_core::registry::types::Fraction; use graphene_core::{Color, Ctx, num_traits}; use log::warn; use math_parser::ast; @@ -632,35 +632,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"))] @@ -669,16 +641,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 +#[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.rs b/node-graph/graph-craft/src/document.rs index 08679ce417..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_core::ops::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_core::ops::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_core::ops::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_core::ops::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_core::ops::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_core::ops::IdentityNode".into()), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), ..Default::default() }; // TODO: Extend test cases to test nested network diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index c8c896290c..a7ce2c566d 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -8,17 +8,19 @@ use graphene_application_io::SurfaceFrame; use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; 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::{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; use std::str::FromStr; pub use std::sync::Arc; - pub struct TaggedValueTypeError; /// Macro to generate the tagged value enum. @@ -29,6 +31,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), @@ -36,6 +39,74 @@ macro_rules! tagged_value { EditorApi(Arc) } + #[derive(Clone, Copy, Debug)] + #[repr(u32)] + pub enum TaggedValueChoice { + None, + Percentage, + $($identifier,)* + } + + impl TaggedValueChoice { + 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 + } + } + } + + 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); 2 + COUNT] = [ + + (TaggedValueChoice::None, VariantMetadata { + name: Cow::Borrowed(stringify!(None)), + label: Cow::Borrowed(stringify!(None)), + 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)), + 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 { @@ -43,6 +114,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), @@ -55,6 +127,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), @@ -65,6 +138,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), @@ -75,6 +149,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), @@ -91,8 +166,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()))), } } @@ -155,6 +228,12 @@ macro_rules! tagged_value { }; } +macro_rules! one { + ($anything:tt) => { + 1 + }; +} + tagged_value! { // =============== // PRIMITIVE TYPES @@ -387,6 +466,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/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 4330976609..7a5e55ec67 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: ops::identity::IDENTIFIER, construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()), input: ProtoNodeInput::None, original_location: OriginalLocation::default(), @@ -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) ] ); } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index ddd71abdbf..ee8d328d27 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -16,6 +16,7 @@ use graphene_std::GraphicElement; use graphene_std::any::DowncastBothNode; 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}; @@ -113,6 +114,16 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => WgpuSurface]), #[cfg(feature = "gpu")]