Skip to content

General Purpose Value Node #2822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
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 {
Expand All @@ -100,7 +100,178 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
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 {
Expand Down Expand Up @@ -915,7 +1086,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
},
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,
},
Expand Down Expand Up @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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 {
Expand Down Expand Up @@ -604,7 +607,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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);
Expand Down Expand Up @@ -939,6 +942,24 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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;
Expand All @@ -958,7 +979,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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;

Expand Down Expand Up @@ -1356,6 +1380,17 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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 => {
Expand Down Expand Up @@ -1394,7 +1429,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> 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);
}
}
Expand Down Expand Up @@ -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<Message>) {
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<WirePathUpdate> {
let mut added_wires = network_interface
.node_graph_input_connectors(breadcrumb_network_path)
Expand Down
Loading
Loading