From 007812077bf9a67574469139e3ec6615cee3d7df Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 5 Jul 2025 20:33:50 -0700 Subject: [PATCH] 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 | 38 ++--- 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, 339 insertions(+), 92 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); +// #[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<'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 ValueNode { +// pub const fn new(value: T) -> ValueNode { +// ValueNode(value) +// } +// } -impl From for ValueNode { - fn from(value: T) -> Self { - ValueNode::new(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")]