From 0fbbffd7933546e43b5009755ccffe2221081dbf Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 10 Nov 2024 12:57:40 -0800 Subject: [PATCH 01/19] Remove imports/exports --- .../document/document_message_handler.rs | 14 +- .../node_graph/document_node_definitions.rs | 1 - .../document/node_graph/node_graph_message.rs | 3 + .../node_graph/node_graph_message_handler.rs | 75 ++-- .../document/node_graph/utility_types.rs | 2 + .../utility_types/network_interface.rs | 336 +++++++++++++----- frontend/src/components/views/Graph.svelte | 37 +- frontend/src/wasm-communication/messages.ts | 1 + 8 files changed, 335 insertions(+), 134 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 303ff572f6..51ea8be13c 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1224,21 +1224,13 @@ impl MessageHandler> for DocumentMessag .navigation_handler .calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz); self.network_interface.set_transform(transform, &self.breadcrumb_network_path); - let imports = self.network_interface.frontend_imports(&self.breadcrumb_network_path).unwrap_or_default(); - let exports = self.network_interface.frontend_exports(&self.breadcrumb_network_path).unwrap_or_default(); - let add_import = self.network_interface.frontend_import_modify(&self.breadcrumb_network_path); - let add_export = self.network_interface.frontend_export_modify(&self.breadcrumb_network_path); - + responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); responses.add(NodeGraphMessage::UpdateEdges); responses.add(NodeGraphMessage::UpdateBoxSelection); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); + responses.add(FrontendMessage::UpdateNodeGraphTransform { transform: Transform { scale: transform.matrix2.x_axis.x, 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 9c1e61dcaa..1105094e22 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 @@ -64,7 +64,6 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy> = /// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built. fn static_nodes() -> Vec { let mut custom = vec![ - // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { identifier: "Default Network", category: "General", 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 80553e730e..b5b10b63a1 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 @@ -98,6 +98,8 @@ pub enum NodeGraphMessage { shift: Key, }, PrintSelectedNodeCoordinates, + RemoveImport { import_index: usize }, + RemoveExport { export_index: usize }, RunDocumentGraph, ForceRunDocumentGraph, SelectedNodesAdd { @@ -180,6 +182,7 @@ pub enum NodeGraphMessage { }, UpdateEdges, UpdateBoxSelection, + UpdateImportsExports, UpdateLayerPanel, UpdateNewNodeGraph, UpdateTypes { 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 2700c93835..e5e4084246 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 @@ -564,16 +564,26 @@ impl<'a> MessageHandler> for NodeGrap return; }; - if modify_import_export.add_export.intersect_point_no_stroke(node_graph_point) { + if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddExport); responses.add(NodeGraphMessage::SendGraph); return; - } else if modify_import_export.add_import.intersect_point_no_stroke(node_graph_point) { + } else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddImport); responses.add(NodeGraphMessage::SendGraph); return; + } else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index }); + responses.add(NodeGraphMessage::SendGraph); + return; + } else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index }); + responses.add(NodeGraphMessage::SendGraph); + return; } if network_interface @@ -1186,6 +1196,12 @@ impl<'a> MessageHandler> for NodeGrap // } // } } + NodeGraphMessage::RemoveImport { import_index: usize } => { + network_interface.remove_import(usize, selection_network_path); + } + NodeGraphMessage::RemoveExport { export_index: usize } => { + network_interface.remove_export(usize, selection_network_path); + } NodeGraphMessage::RunDocumentGraph => { responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); } @@ -1230,16 +1246,7 @@ impl<'a> MessageHandler> for NodeGrap let wires = Self::collect_wires(network_interface, breadcrumb_network_path); let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path); - let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); - let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); - let add_import = network_interface.frontend_import_modify(breadcrumb_network_path); - let add_export = network_interface.frontend_export_modify(breadcrumb_network_path); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(FrontendMessage::UpdateNodeGraph { nodes, wires }); responses.add(FrontendMessage::UpdateLayerWidths { layer_widths, @@ -1253,16 +1260,7 @@ impl<'a> MessageHandler> for NodeGrap if graph_view_overlay_open { network_interface.set_grid_aligned_edges(DVec2::new(ipp.viewport_bounds.bottom_right.x - ipp.viewport_bounds.top_left.x, 0.), breadcrumb_network_path); // Send the new edges to the frontend - let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); - let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); - let add_import = network_interface.frontend_import_modify(breadcrumb_network_path); - let add_export = network_interface.frontend_export_modify(breadcrumb_network_path); - responses.add(FrontendMessage::UpdateImportsExports { - imports, - exports, - add_import, - add_export, - }); + responses.add(NodeGraphMessage::UpdateImportsExports); } } NodeGraphMessage::SetInputValue { node_id, input_index, value } => { @@ -1552,6 +1550,39 @@ impl<'a> MessageHandler> for NodeGrap responses.add(FrontendMessage::UpdateBox { box_selection }) } } + NodeGraphMessage::UpdateImportsExports => { + let imports = network_interface.frontend_imports(breadcrumb_network_path).unwrap_or_default(); + let exports = network_interface.frontend_exports(breadcrumb_network_path).unwrap_or_default(); + let add_import = network_interface + .frontend_import_export_modify( + |modify_import_export_click_target| modify_import_export_click_target.add_import_export.output_ports().collect::>(), + breadcrumb_network_path, + ) + .into_iter() + .next(); + let add_export = network_interface + .frontend_import_export_modify( + |modify_import_export_click_target| modify_import_export_click_target.add_import_export.input_ports().collect::>(), + breadcrumb_network_path, + ) + .into_iter() + .next(); + // let remove_imports = network_interface.frontend_import_export_modify( + // |modify_import_export_click_target| modify_import_export_click_target.remove_imports_exports.output_ports().collect::>(), + // breadcrumb_network_path, + // ); + // let remove_exports = network_interface.frontend_import_export_modify( + // |modify_import_export_click_target| modify_import_export_click_target.remove_imports_exports.input_ports().collect::>(), + // breadcrumb_network_path, + // ); + responses.add(FrontendMessage::UpdateImportsExports { + imports, + exports, + add_import, + add_export, + }); + } + NodeGraphMessage::UpdateLayerPanel => { Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses); } diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index 9a19136a83..1a2372c511 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -180,6 +180,8 @@ pub struct FrontendClickTargets { pub all_nodes_bounding_box: String, #[serde(rename = "importExportsBoundingBox")] pub import_exports_bounding_box: String, + #[serde(rename = "modifyImportExport")] + pub modify_import_export: Vec, } #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] 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 95dca57036..2c4ef01116 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -837,30 +837,18 @@ impl NodeNetworkInterface { }) } - pub fn frontend_import_modify(&mut self, network_path: &[NodeId]) -> Option<(i32, i32)> { - (!network_path.is_empty()) - .then(|| { - self.modify_import_export(network_path).and_then(|modify_import_export_click_target| { - modify_import_export_click_target - .add_export - .bounding_box() - .map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32)) - }) - }) - .flatten() - } - - pub fn frontend_export_modify(&mut self, network_path: &[NodeId]) -> Option<(i32, i32)> { - (!network_path.is_empty()) - .then(|| { - self.modify_import_export(network_path).and_then(|modify_import_export_click_target| { - modify_import_export_click_target - .add_import - .bounding_box() - .map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32)) - }) + pub fn frontend_import_export_modify(&mut self, get_ports: F, network_path: &[NodeId]) -> Vec<(i32, i32)> + where + F: FnOnce(&ModifyImportExportClickTarget) -> Vec<&(usize, ClickTarget)>, + { + self.modify_import_export(network_path) + .map(|modify_import_export_click_target| { + get_ports(&modify_import_export_click_target) + .iter() + .filter_map(|(_, click_target)| click_target.bounding_box().map(|bounding_box| (bounding_box[0].x as i32, bounding_box[0].y as i32))) + .collect() }) - .flatten() + .unwrap_or_default() } pub fn height_from_click_target(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { @@ -1960,48 +1948,82 @@ impl NodeNetworkInterface { return; }; - let viewport_top_right = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(rounded_network_edge_distance.exports_to_edge_distance); - let offset_from_top_right = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(4. * GRID_SIZE as f64, 0.) - }; + let mut add_import_export = Ports::new(); + let mut remove_imports_exports = Ports::new(); - let bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_right; - let export_top_right = DVec2::new(viewport_top_right.x.max(bounding_box_top_right.x), viewport_top_right.y.min(bounding_box_top_right.y)); - let add_export_center = export_top_right + DVec2::new(0., network.exports.len() as f64 * 24.); - let add_export = ClickTarget::new(Subpath::new_ellipse(add_export_center - DVec2::new(8., 8.), add_export_center + DVec2::new(8., 8.)), 0.); + if !network_path.is_empty() { + let viewport_top_right = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(rounded_network_edge_distance.exports_to_edge_distance); + let offset_from_top_right = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(2. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(4. * GRID_SIZE as f64, 0.) + }; - let viewport_top_left = network_metadata - .persistent_metadata - .navigation_metadata - .node_graph_to_viewport - .inverse() - .transform_point2(rounded_network_edge_distance.imports_to_edge_distance); + let bounding_box_top_right = DVec2::new((all_nodes_bounding_box[1].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_right; + let export_top_right: DVec2 = DVec2::new(viewport_top_right.x.max(bounding_box_top_right.x), viewport_top_right.y.min(bounding_box_top_right.y)); + let add_export_center = export_top_right + DVec2::new(0., network.exports.len() as f64 * 24.); + let add_export = ClickTarget::new( + Subpath::new_rounded_rect(add_export_center - DVec2::new(12., 12.), add_export_center + DVec2::new(12., 12.), [3.; 4]), + 0., + ); + add_import_export.insert_custom_input_port(0, add_export); - let offset_from_top_left = if network - .exports - .first() - .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) - { - DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) - } else { - DVec2::new(-4. * GRID_SIZE as f64, 0.) - }; + let viewport_top_left = network_metadata + .persistent_metadata + .navigation_metadata + .node_graph_to_viewport + .inverse() + .transform_point2(rounded_network_edge_distance.imports_to_edge_distance); - let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; - let import_top_left = DVec2::new(viewport_top_left.x.min(bounding_box_top_left.x), viewport_top_left.y.min(bounding_box_top_left.y)); - let add_import_center = import_top_left + DVec2::new(0., self.number_of_displayed_imports(network_path) as f64 * 24.); - let add_import = ClickTarget::new(Subpath::new_ellipse(add_import_center - DVec2::new(8., 8.), add_import_center + DVec2::new(8., 8.)), 0.); + let offset_from_top_left = if network + .exports + .first() + .is_some_and(|export| export.as_node().is_some_and(|export_node| self.is_layer(&export_node, network_path))) + { + DVec2::new(-4. * GRID_SIZE as f64, -2. * GRID_SIZE as f64) + } else { + DVec2::new(-4. * GRID_SIZE as f64, 0.) + }; + + let bounding_box_top_left = DVec2::new((all_nodes_bounding_box[0].x / 24. + 0.5).floor() * 24., (all_nodes_bounding_box[0].y / 24. + 0.5).floor() * 24.) + offset_from_top_left; + let import_top_left = DVec2::new(viewport_top_left.x.min(bounding_box_top_left.x), viewport_top_left.y.min(bounding_box_top_left.y)); + let add_import_center = import_top_left + DVec2::new(0., self.number_of_displayed_imports(network_path) as f64 * 24.); + let add_import = ClickTarget::new( + Subpath::new_rounded_rect(add_import_center - DVec2::new(12., 12.), add_import_center + DVec2::new(12., 12.), [3.; 4]), + 0., + ); + add_import_export.insert_custom_output_port(0, add_import); + + let Some(import_exports) = self.import_export_ports(network_path) else { + log::error!("Could not get import_export_ports in load_modify_import_export"); + return; + }; + + for (export_index, export_click_target) in import_exports.input_ports() { + let Some(export_bounding_box) = export_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + remove_imports_exports.insert_input_port_at_center(*export_index, (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(16., 0.)); + } + + for (import_index, import_click_target) in import_exports.output_ports() { + let Some(import_bounding_box) = import_click_target.bounding_box() else { + log::error!("Could not get export bounding box in load_modify_import_export"); + continue; + }; + remove_imports_exports.insert_output_port_at_center(*import_index, (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-16., 0.)); + } + } let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Could not get current network in load_modify_import_export"); @@ -2009,12 +2031,9 @@ impl NodeNetworkInterface { }; network_metadata.transient_metadata.modify_import_export = TransientMetadata::Loaded(ModifyImportExportClickTarget { - add_export, - add_import, - remove_imports: Vec::new(), - remove_exports: Vec::new(), - move_import: Vec::new(), - move_export: Vec::new(), + add_import_export, + remove_imports_exports, + move_imports_exports: Ports::new(), }); } @@ -2693,6 +2712,19 @@ impl NodeNetworkInterface { let mut import_exports_bounding_box = String::new(); let _ = import_exports_target.subpath_to_svg(&mut import_exports_bounding_box, DAffine2::IDENTITY); + let mut modify_import_export = Vec::new(); + if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) { + for click_target in modify_import_export_click_targets + .add_import_export + .click_targets() + .chain(modify_import_export_click_targets.remove_imports_exports.click_targets()) + .chain(modify_import_export_click_targets.move_imports_exports.click_targets()) + { + let mut remove_string = String::new(); + let _ = click_target.subpath().subpath_to_svg(&mut remove_string, DAffine2::IDENTITY); + modify_import_export.push(remove_string); + } + } FrontendClickTargets { node_click_targets, layer_click_targets, @@ -2700,6 +2732,7 @@ impl NodeNetworkInterface { icon_click_targets, all_nodes_bounding_box, import_exports_bounding_box, + modify_import_export, } } @@ -3226,10 +3259,6 @@ impl NodeNetworkInterface { log::error!("Cannot add import for document network"); return; }; - // Set the node to be a non layer if it is no longer eligible to be a layer - if !self.is_eligible_to_be_layer(&node_id, &encapsulating_network_path) && self.is_layer(&node_id, &encapsulating_network_path) { - self.set_to_node_or_layer(&node_id, &encapsulating_network_path, false); - } let Some(network) = self.network_mut(&encapsulating_network_path) else { log::error!("Could not get nested network in insert_input"); @@ -3249,6 +3278,11 @@ impl NodeNetworkInterface { self.transaction_modified(); + // Set the node to be a non layer if it is no longer eligible to be a layer + if !self.is_eligible_to_be_layer(&node_id, &encapsulating_network_path) && self.is_layer(&node_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&node_id, &encapsulating_network_path, false); + } + let Some(node_metadata) = self.node_metadata_mut(&node_id, &encapsulating_network_path) else { log::error!("Could not get node_metadata in insert_input"); return; @@ -3259,27 +3293,134 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.input_names.insert(insert_index as usize, input_name); } - // Update the internal network import ports and outwards connections (if has a network implementation) - if let Some(internal_network) = &mut node_metadata.persistent_metadata.network_metadata { - internal_network.transient_metadata.import_export_ports.unload(); - internal_network.transient_metadata.outward_wires.unload(); + // Update the metadata for the encapsulating node + self.unload_node_click_targets(&node_id, &encapsulating_network_path); + self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if encapsulating_network_path.is_empty() && (insert_index == 0 || insert_index == 1) { + self.load_structure(); } - // Update the click targets for the node - self.unload_node_click_targets(&node_id, &encapsulating_network_path); + // Unload the metadata for the nested network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); + } + + // First disconnects the export, then removes it + pub fn remove_export(&mut self, export_index: usize, network_path: &[NodeId]) { + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Cannot remove export for document network"); + return; + }; - // Update the transient network metadata bounding box for all nodes and outward wires + self.disconnect_input(&InputConnector::Export(export_index), network_path); + + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in add_export"); + return; + }; + + network.exports.remove(export_index); + + self.transaction_modified(); + + // There will not be an encapsulating node if the network is the document network + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating node metadata in remove_export"); + return; + }; + encapsulating_node_metadata.persistent_metadata.output_names.remove(export_index); + + // Update the metadata for the encapsulating node + self.unload_outward_wires(&encapsulating_network_path); + self.unload_node_click_targets(&parent_id, &encapsulating_network_path); self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if !self.is_eligible_to_be_layer(&parent_id, &encapsulating_network_path) && self.is_layer(&parent_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&parent_id, &encapsulating_network_path, false); + } + if encapsulating_network_path.is_empty() { + self.load_structure(); + } // Unload the metadata for the nested network self.unload_outward_wires(network_path); self.unload_import_export_ports(network_path); self.unload_modify_import_export(network_path); + } - // If the input is inserted as the first input, then it may have affected the document metadata structure - if encapsulating_network_path.is_empty() && (insert_index == 0 || insert_index == 1) { + // First disconnects the import, then removes it + pub fn remove_import(&mut self, import_index: usize, network_path: &[NodeId]) { + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Cannot remove export for document network"); + return; + }; + + let number_of_inputs = self.number_of_inputs(&parent_id, &encapsulating_network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in remove_import"); + return; + }; + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + let mut new_import_mapping = Vec::new(); + for i in (import_index + 1)..number_of_inputs { + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(i)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + for upstream_input_wire in outward_wires_for_import { + new_import_mapping.push((OutputConnector::Import(i - 1), upstream_input_wire)); + } + } + + // Disconnect all upstream connections + for outward_wire in outward_wires_for_import { + self.disconnect_input(&outward_wire, network_path); + } + // Shift inputs connected to to imports at a higher index down one + for (output_connector, input_wire) in new_import_mapping { + self.create_wire(&output_connector, &input_wire, network_path); + } + + let Some(network) = self.network_mut(&encapsulating_network_path) else { + log::error!("Could not get parent node in remove_import"); + return; + }; + let Some(node) = network.nodes.get_mut(&parent_id) else { + log::error!("Could not get node in remove_import"); + return; + }; + + node.inputs.remove(import_index); + + self.transaction_modified(); + + // There will not be an encapsulating node if the network is the document network + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating node metadata in remove_export"); + return; + }; + encapsulating_node_metadata.persistent_metadata.input_names.remove(import_index); + + // Update the metadata for the encapsulating node + self.unload_outward_wires(&encapsulating_network_path); + self.unload_node_click_targets(&parent_id, &encapsulating_network_path); + self.unload_all_nodes_bounding_box(&encapsulating_network_path); + if !self.is_eligible_to_be_layer(&parent_id, &encapsulating_network_path) && self.is_layer(&parent_id, &encapsulating_network_path) { + self.set_to_node_or_layer(&parent_id, &encapsulating_network_path, false); + } + if encapsulating_network_path.is_empty() { self.load_structure(); } + + // Unload the metadata for the nested network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts @@ -5317,14 +5458,30 @@ impl Ports { .chain(self.output_ports.iter().map(|(_, click_target)| click_target)) } - pub fn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) { + pub fn input_ports(&self) -> impl Iterator { + self.input_ports.iter() + } + + pub fn output_ports(&self) -> impl Iterator { + self.output_ports.iter() + } + + fn insert_input_port_at_center(&mut self, input_index: usize, center: DVec2) { let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.)); - self.input_ports.push((input_index, ClickTarget::new(subpath, 0.))); + self.insert_custom_input_port(input_index, ClickTarget::new(subpath, 0.)); } - pub fn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) { + fn insert_custom_input_port(&mut self, input_index: usize, click_target: ClickTarget) { + self.input_ports.push((input_index, click_target)); + } + + fn insert_output_port_at_center(&mut self, output_index: usize, center: DVec2) { let subpath = Subpath::new_ellipse(center - DVec2::new(8., 8.), center + DVec2::new(8., 8.)); - self.output_ports.push((output_index, ClickTarget::new(subpath, 0.))); + self.insert_custom_output_port(output_index, ClickTarget::new(subpath, 0.)); + } + + fn insert_custom_output_port(&mut self, output_index: usize, click_target: ClickTarget) { + self.output_ports.push((output_index, click_target)); } fn insert_node_input(&mut self, input_index: usize, row_index: usize, node_top_left: DVec2) { @@ -5509,15 +5666,12 @@ pub struct NodeNetworkTransientMetadata { #[derive(Debug, Clone)] pub struct ModifyImportExportClickTarget { - // Plus icon that appears below all imports/exports - pub add_import: ClickTarget, - pub add_export: ClickTarget, + // Plus icon that appears below all imports/exports, except in the document network + pub add_import_export: Ports, // Subtract icon that appears when hovering over an import/export - pub remove_imports: Vec, - pub remove_exports: Vec, + pub remove_imports_exports: Ports, // Grip drag icon that appears when hovering over an import/export - pub move_import: Vec, - pub move_export: Vec, + pub move_imports_exports: Ports, } #[derive(Debug, Clone)] diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index bfa6592113..9dc214e040 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -394,6 +394,9 @@ {/each} + {#each $nodeGraph.clickTargets.modifyImportExport as pathString} + + {/each} {/if} @@ -437,15 +440,22 @@ {/if} -

{outputMetadata.name}

+
+ { + /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ + }} + /> +
+

{outputMetadata.name}

{/each} {#if $nodeGraph.addImport !== undefined}
{ /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} @@ -472,13 +482,20 @@ {/if} -

{inputMetadata.name}

+
+ { + /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ + }} + /> +
+

{inputMetadata.name}

{/each} {#if $nodeGraph.addExport !== undefined}
{ @@ -878,6 +895,10 @@ .all-nodes-bounding-box { stroke: purple; } + + .modify-import-export { + stroke: orange; + } } } @@ -917,8 +938,6 @@ } .plus { - margin-top: -4px; - margin-left: -4px; position: absolute; top: calc(var(--offset-top) * 24px); left: calc(var(--offset-left) * 24px); diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index c6fdf9167d..510e53e271 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -174,6 +174,7 @@ export type FrontendClickTargets = { readonly iconClickTargets: string[]; readonly allNodesBoundingBox: string; readonly importExportsBoundingBox: string; + readonly modifyImportExport: string[]; }; export type ContextMenuInformation = { From 303db62143117392a56764e4c5c5e1a35b39639b Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 16 Nov 2024 08:33:15 -0800 Subject: [PATCH 02/19] WIP: Autogenerated properties --- .../document/document_message_handler.rs | 7 +- .../node_graph/document_node_definitions.rs | 118 +++--------------- .../node_graph/node_graph_message_handler.rs | 63 +++++----- .../document/node_graph/node_properties.rs | 70 +++++++---- .../properties_panel/utility_types.rs | 2 +- .../utility_types/network_interface.rs | 55 ++++++-- node-graph/gcore/src/registry.rs | 2 + 7 files changed, 151 insertions(+), 166 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 51ea8be13c..c1f11376f5 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -194,7 +194,7 @@ impl MessageHandler> for DocumentMessag } DocumentMessage::PropertiesPanel(message) => { let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData { - network_interface: &self.network_interface, + network_interface: &mut self.network_interface, selection_network_path: &self.selection_network_path, document_name: self.name.as_str(), executor, @@ -391,6 +391,7 @@ impl MessageHandler> for DocumentMessag self.selection_network_path.clone_from(&self.breadcrumb_network_path); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::ZoomCanvasToFitAll); + responses.add(NodeGraphMessage::SetGridAlignedEdges); } DocumentMessage::Escape => { if self.node_graph_handler.drag_start.is_some() { @@ -1224,13 +1225,13 @@ impl MessageHandler> for DocumentMessag .navigation_handler .calculate_offset_transform(ipp.viewport_bounds.center(), &network_metadata.persistent_metadata.navigation_metadata.node_graph_ptz); self.network_interface.set_transform(transform, &self.breadcrumb_network_path); - + responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); responses.add(NodeGraphMessage::UpdateEdges); responses.add(NodeGraphMessage::UpdateBoxSelection); responses.add(NodeGraphMessage::UpdateImportsExports); - + responses.add(FrontendMessage::UpdateNodeGraphTransform { transform: Transform { scale: transform.matrix2.x_axis.x, 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 1105094e22..6f8983665d 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 @@ -1,9 +1,8 @@ use super::node_properties; use super::utility_types::FrontendNodeType; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::node_properties::node_no_properties; use crate::messages::portfolio::document::utility_types::network_interface::{ - DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, + DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, PropertiesRow, }; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::Message; @@ -33,7 +32,7 @@ pub struct NodePropertiesContext<'a> { pub persistent_data: &'a PersistentData, pub responses: &'a mut VecDeque, pub executor: &'a mut NodeGraphExecutor, - pub network_interface: &'a NodeNetworkInterface, + pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub document_name: &'a str, } @@ -49,7 +48,6 @@ pub struct DocumentNodeDefinition { /// Definition specific data. In order for the editor to access this data, the reference will be used. pub category: &'static str, - pub properties: &'static (dyn Fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec + Sync), /// User-facing description of the node's functionality. pub description: Cow<'static, str>, @@ -78,7 +76,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("A default node network you can use to create your own custom nodes."), - properties: &node_properties::node_no_properties, }, // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { @@ -91,13 +88,14 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".to_string().into()], + custom_properties: &|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through"), output_names: vec!["Out".to_string()], ..Default::default() }, }, + description: Cow::Borrowed("The identity node passes its data through. You can use this to organize your node graph."), - properties: &|_document_node, _node_id, _context| node_properties::string_properties("The identity node simply passes its data through"), }, // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { @@ -112,13 +110,13 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".to_string().into()], + custom_properties: &|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it."), output_names: vec!["Out".to_string()], ..Default::default() }, }, description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."), - properties: &|_document_node, _node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it"), }, DocumentNodeDefinition { identifier: "Merge", @@ -225,7 +223,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("The Merge node combines graphical data through composition."), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Artboard", @@ -336,7 +333,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."), - properties: &node_properties::artboard_properties, }, DocumentNodeDefinition { identifier: "Load Image", @@ -417,7 +413,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Loads an image from a given url."), - properties: &node_properties::load_image_properties, }, DocumentNodeDefinition { identifier: "Create Canvas", @@ -482,7 +477,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates a new canvas object."), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Draw Canvas", @@ -575,7 +569,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Draws raster data to a canvas element."), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Rasterize", @@ -667,7 +660,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Rasterizes the given vector data"), - properties: &node_properties::rasterize_properties, }, DocumentNodeDefinition { identifier: "Image Frame", @@ -702,7 +694,8 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Transform".to_string()], + input_properties: vec!["Image".to_string().into(), "Transform".to_string().into()], + custom_properties: Some(&|_node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform")), output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -736,7 +729,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates an embedded image with the given transform."), - properties: &|_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"), }, DocumentNodeDefinition { identifier: "Noise Pattern", @@ -815,7 +807,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Generates different noise patterns."), - properties: &node_properties::noise_pattern_properties, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. // TODO: Auto-generate this from its proto node macro @@ -838,7 +829,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::mask_properties, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. // TODO: Auto-generate this from its proto node macro @@ -862,7 +852,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::insert_channel_properties, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. DocumentNodeDefinition { @@ -887,7 +876,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Split Channels", @@ -1001,7 +989,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Brush", @@ -1070,7 +1057,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Memoize", @@ -1089,7 +1075,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Memoize Impure", @@ -1108,7 +1093,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Image", @@ -1133,7 +1117,8 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string()], + input_properties: vec!["Image".to_string()], + custom_properties: Some(&|_node_id, _context| node_properties::string_properties("A bitmap image is embedded in this node")), output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1157,7 +1142,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &|_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"), }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1238,7 +1222,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Storage", @@ -1317,7 +1300,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Create Output Buffer", @@ -1396,7 +1378,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1485,7 +1466,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1508,7 +1488,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::node_no_properties, + description: Cow::Borrowed("TODO"), }, #[cfg(feature = "gpu")] @@ -1589,7 +1569,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1669,7 +1648,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1735,7 +1713,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1806,7 +1783,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1887,7 +1863,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1909,7 +1884,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Extract", @@ -1927,7 +1901,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { // Aims for interoperable compatibility with: @@ -1953,7 +1926,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::brightness_contrast_properties, }, // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure @@ -1977,7 +1949,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::curves_properties, }, (*IMAGINATE_NODE).clone(), DocumentNodeDefinition { @@ -2001,7 +1972,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::line_properties, }, DocumentNodeDefinition { identifier: "Spline", @@ -2024,7 +1994,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::spline_properties, }, DocumentNodeDefinition { identifier: "Path", @@ -2095,7 +2064,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::node_no_properties, }, DocumentNodeDefinition { identifier: "Text", @@ -2131,7 +2099,6 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), - properties: &node_properties::text_properties, }, DocumentNodeDefinition { identifier: "Transform", @@ -2217,7 +2184,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::transform_properties, + description: Cow::Borrowed("TODO"), }, DocumentNodeDefinition { @@ -2287,7 +2254,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::boolean_operation_properties, + description: Cow::Borrowed("TODO"), }, DocumentNodeDefinition { @@ -2325,7 +2292,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::copy_to_points_properties, + description: Cow::Borrowed("TODO"), }, DocumentNodeDefinition { @@ -2422,7 +2389,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::sample_points_properties, + description: Cow::Borrowed("TODO"), }, DocumentNodeDefinition { @@ -2495,7 +2462,7 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::poisson_disk_points_properties, + description: Cow::Borrowed("TODO"), }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. @@ -2514,28 +2481,11 @@ fn static_nodes() -> Vec { ..Default::default() }, }, - properties: &node_properties::index_properties, + description: Cow::Borrowed("TODO"), }, ]; - type PropertiesLayout = &'static (dyn Fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec + Sync); - let properties_overrides = [ - ("graphene_core::raster::adjustments::ChannelMixerNode", &node_properties::channel_mixer_properties as PropertiesLayout), - ("graphene_core::vector::FillNode", &node_properties::fill_properties as PropertiesLayout), - ("graphene_core::vector::StrokeNode", &node_properties::stroke_properties as PropertiesLayout), - ("graphene_core::vector::OffsetPathNode", &node_properties::offset_path_properties as PropertiesLayout), - ( - "graphene_core::raster::adjustments::SelectiveColorNode", - &node_properties::selective_color_properties as PropertiesLayout, - ), - ("graphene_core::raster::ExposureNode", &node_properties::exposure_properties as PropertiesLayout), - ("graphene_core::vector::generator_nodes::RectangleNode", &node_properties::rectangle_properties as PropertiesLayout), - ("graphene_core::vector::AssignColorsNode", &node_properties::assign_colors_properties as PropertiesLayout), - ] - .into_iter() - .collect::>(); - // Remove struct generics for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() { let NodeTemplate { @@ -2601,33 +2551,6 @@ fn static_nodes() -> Vec { }) .collect(); - let properties = match properties_overrides.get(id.as_str()) { - Some(properties_function) => *properties_function, - None => { - let field_types: Vec<_> = fields.iter().zip(first_node_io.inputs.iter()).map(|(field, ty)| (field.clone(), ty.clone())).collect(); - let properties = move |document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext| { - let rows: Vec<_> = field_types - .iter() - .enumerate() - .skip(1) - .filter(|(_, (field, _))| !matches!(&field.value_source, ValueSource::Scope(_))) - .flat_map(|(index, (field, ty))| { - let number_options = (field.number_min, field.number_max, field.number_mode_range); - - node_properties::property_from_type(document_node, node_id, index, field.name, ty, context, number_options) - }) - .collect(); - - if rows.is_empty() { - return node_no_properties(document_node, node_id, context); - } - - rows - }; - Box::leak(Box::new(properties)) as PropertiesLayout - } - }; - let node = DocumentNodeDefinition { identifier: display_name, node_template: NodeTemplate { @@ -2640,7 +2563,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: fields.iter().map(|f| f.name.to_string()).collect(), + input_properties: fields.iter().map(|f| f.name.to_string().into()).collect(), output_names: vec![output_type.to_string()], has_primary_output: true, locked: false, @@ -2649,7 +2572,6 @@ fn static_nodes() -> Vec { }, category: category.unwrap_or("UNCATEGORIZED"), description: Cow::Borrowed(description), - properties, }; custom.push(node); } @@ -2774,7 +2696,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN ..Default::default() }, }, - properties: &node_properties::imaginate_properties, + description: Cow::Borrowed("TODO"), }); 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 e5e4084246..a2c91ce7e0 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 @@ -9,7 +9,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{ - self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, + self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, PropertiesRow, TypeSource }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::prelude::*; @@ -99,8 +99,14 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] }); } - NodeGraphMessage::AddImport => network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, String::new(), breadcrumb_network_path), - NodeGraphMessage::AddExport => network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, String::new(), breadcrumb_network_path), + NodeGraphMessage::AddImport => { + network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, String::new(), breadcrumb_network_path); + responses.add(NodeGraphMessage::SendGraph); + } + NodeGraphMessage::AddExport => { + network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, String::new(), breadcrumb_network_path); + responses.add(NodeGraphMessage::SendGraph); + } NodeGraphMessage::Init => { responses.add(BroadcastMessage::SubscribeEvent { on: BroadcastEvent::SelectionChanged, @@ -567,22 +573,18 @@ impl<'a> MessageHandler> for NodeGrap if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddExport); - responses.add(NodeGraphMessage::SendGraph); return; } else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::AddImport); - responses.add(NodeGraphMessage::SendGraph); return; } else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index }); - responses.add(NodeGraphMessage::SendGraph); return; } else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index }); - responses.add(NodeGraphMessage::SendGraph); return; } @@ -1198,9 +1200,13 @@ impl<'a> MessageHandler> for NodeGrap } NodeGraphMessage::RemoveImport { import_index: usize } => { network_interface.remove_import(usize, selection_network_path); + responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::RunDocumentGraph); } NodeGraphMessage::RemoveExport { export_index: usize } => { network_interface.remove_export(usize, selection_network_path); + responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::RunDocumentGraph); } NodeGraphMessage::RunDocumentGraph => { responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); @@ -1870,10 +1876,6 @@ impl NodeGraphMessageHandler { /// Collate the properties panel sections for a node graph pub fn collate_properties(context: &mut NodePropertiesContext) -> Vec { // If the selected nodes are in the document network, use the document network. Otherwise, use the nested network - let Some(network) = context.network_interface.network(context.selection_network_path) else { - warn!("No network in collate_properties"); - return Vec::new(); - }; let Some(selected_nodes) = context.network_interface.selected_nodes(context.selection_network_path) else { warn!("No selected nodes in collate_properties"); return Vec::new(); @@ -1901,17 +1903,15 @@ impl NodeGraphMessageHandler { 0 => { let selected_nodes = nodes .iter() - .filter_map(|node_id| { - network.nodes.get(node_id).map(|node| { - let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { - node.persistent_metadata.pinned - } else { - error!("Could not get node {node_id} in collate_properties"); - false - }; + .map(|node_id| { + let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { + node.persistent_metadata.pinned + } else { + error!("Could not get node {node_id} in collate_properties"); + false + }; - node_properties::generate_node_properties(node, *node_id, pinned, context) - }) + node_properties::generate_node_properties(*node_id, pinned, context) }) .collect::>(); if !selected_nodes.is_empty() { @@ -1931,11 +1931,18 @@ impl NodeGraphMessageHandler { ], }]; + let Some(network) = context.network_interface.network(context.selection_network_path) else { + warn!("No network in collate_properties"); + return Vec::new(); + }; // And if no nodes are selected, show properties for all pinned nodes let pinned_node_properties = network .nodes + .keys() + .cloned() + .collect::>() .iter() - .filter_map(|(node_id, node)| { + .filter_map(|node_id| { let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { node.persistent_metadata.pinned } else { @@ -1944,7 +1951,7 @@ impl NodeGraphMessageHandler { }; if pinned { - Some(node_properties::generate_node_properties(node, *node_id, pinned, context)) + Some(node_properties::generate_node_properties(*node_id, pinned, context)) } else { None } @@ -2014,8 +2021,9 @@ impl NodeGraphMessageHandler { !context.network_interface.is_layer(node_id, context.selection_network_path) } }) - .filter_map(|(_, node_id)| network.nodes.get(&node_id).map(|node| (node, node_id))) - .map(|(node, node_id)| { + .collect::>() + .iter() + .map(|(_, node_id)| { let pinned = if let Some(node) = context.network_interface.node_metadata(&node_id, context.selection_network_path) { node.persistent_metadata.pinned } else { @@ -2023,7 +2031,7 @@ impl NodeGraphMessageHandler { false }; - node_properties::generate_node_properties(node, node_id, pinned, context) + node_properties::generate_node_properties(*node_id, pinned, context) }) .collect::>(); @@ -2440,8 +2448,7 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: } // Get the name from the metadata here (since it also requires a reference to the `network_interface`) - let name = network_interface.input_name(&node_id, index, breadcrumb_network_path); - + let name = network_interface.input_properties_row(&node_id, index, breadcrumb_network_path).cloned().map(|properties_row|properties_row.input_name).filter(|s| !s.is_empty()); // Get the output connector that feeds into this input (done here as well for simplicity) let connector = OutputConnector::from_input(input); 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 822d8997fb..b2a65224e8 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -3,6 +3,7 @@ use super::document_node_definitions::{NodePropertiesContext, IMAGINATE_NODE}; use super::utility_types::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; +use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::*; use graph_craft::document::value::TaggedValue; @@ -87,16 +88,30 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na widgets } -pub(crate) fn property_from_type( - document_node: &DocumentNode, - node_id: NodeId, - index: usize, - name: &str, - ty: &Type, - _context: &mut NodePropertiesContext, - number_options: (Option, Option, Option<(f64, f64)>), -) -> Vec { - let (mut number_min, mut number_max, range) = number_options; +pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, context: &mut NodePropertiesContext) -> Vec { + let Some(input_properties_row) = context.network_interface.input_properties_row(&node_id, index, context.selection_network_path) else { + log::warn!("A widget failed to be built for node {node_id}, index {index} because the input connector could not be determined"); + return vec![]; + }; + + // Early return if input is hidden + if input_properties_row.hidden { + return vec![]; + } + + let name = &input_properties_row.input_name; + + let Some(network) = context.network_interface.network(context.selection_network_path) else { + log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined"); + return vec![]; + }; + + let Some(document_node) = network.nodes.get(&node_id) else { + log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist"); + return vec![]; + }; + + let (mut number_min, mut number_max, range) = (None, None, None); let mut number_input = NumberInput::default(); if let Some((range_start, range_end)) = range { number_min = Some(range_start); @@ -232,7 +247,7 @@ 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(document_node, node_id, index, name, out, _context, number_options), + Type::Fn(_, out) => return property_from_type(node_id, index, out, context), Type::Future(_) => vec![TextLabel::new("Future type (not supported)").widget_holder()].into(), }; extra_widgets.push(widgets); @@ -2239,7 +2254,7 @@ fn unknown_node_properties(reference: &String) -> Vec { string_properties(format!("Node '{}' cannot be found in library", reference)) } -pub(crate) fn node_no_properties(_document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { string_properties(if context.network_interface.is_layer(&node_id, context.selection_network_path) { "Layer has no properties" } else { @@ -2253,20 +2268,27 @@ pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _c vec![LayoutGroup::Row { widgets: index }] } -pub(crate) fn generate_node_properties(document_node: &DocumentNode, node_id: NodeId, pinned: bool, context: &mut NodePropertiesContext) -> LayoutGroup { - let reference = context.network_interface.reference(&node_id, context.selection_network_path).clone(); - let layout = if let Some(ref reference) = reference { - match super::document_node_definitions::resolve_document_node_type(reference) { - Some(document_node_type) => (document_node_type.properties)(document_node, node_id, context), - None => unknown_node_properties(reference), - } - } else { - node_no_properties(document_node, node_id, context) - }; +pub(crate) fn generate_node_properties(node_id: NodeId, pinned: bool, context: &mut NodePropertiesContext) -> LayoutGroup { + let mut layout = Vec::new(); + if let Some(custom_properties) = context.network_interface.custom_properties(&node_id, context.selection_network_path) { + layout = custom_properties(node_id, context); + } else { + let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); + for input_index in 0..number_of_inputs { + let input_type = context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path); + let row = property_from_type(node_id, input_index, &input_type.0, context); + layout.extend(row); + } + if layout.is_empty() { + layout = node_no_properties(node_id, context); + } + } + let name = context.network_interface.reference(&node_id, context.selection_network_path).clone().unwrap_or_default(); + let visible = context.network_interface.is_visible(&node_id, context.selection_network_path); LayoutGroup::Section { - name: reference.unwrap_or_default(), - visible: document_node.visible, + name, + visible, pinned, id: node_id.0, layout, diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_types.rs b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs index 74dd563ddb..e2ffdacdfd 100644 --- a/editor/src/messages/portfolio/document/properties_panel/utility_types.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_types.rs @@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::node_graph_executor::NodeGraphExecutor; pub struct PropertiesPanelMessageHandlerData<'a> { - pub network_interface: &'a NodeNetworkInterface, + pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], pub document_name: &'a str, pub executor: &'a mut NodeGraphExecutor, 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 2c4ef01116..5bd360b2f4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3,9 +3,10 @@ use super::misc::PTZ; use super::nodes::SelectedNodes; use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; -use crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_document_node_type, DocumentNodeDefinition}; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_document_node_type, DocumentNodeDefinition, NodePropertiesContext}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::tool_messages::tool_prelude::LayoutGroup; use bezier_rs::Subpath; use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; @@ -1036,14 +1037,15 @@ impl NodeNetworkInterface { .and_then(|node_metadata| node_metadata.persistent_metadata.reference.as_ref().map(|reference| reference.to_string())) } - // None means that the type will be used - pub fn input_name(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option { + pub fn input_properties_row(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&PropertiesRow> { self.node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_names.get(index)) - .cloned() - .filter(|s| !s.is_empty()) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index)) } + pub fn custom_properties(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync)> { + self.node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.custom_properties) + } // Use frontend display name instead fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { @@ -2073,7 +2075,7 @@ impl NodeNetworkInterface { let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; // TODO: Eventually replace node graph top right with the footprint when trying to get the network edge distance let node_graph_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; - + log::debug!("Node graph top right is {node_graph_top_right:?}"); let target_exports_distance = node_graph_to_viewport.inverse().transform_point2(DVec2::new( node_graph_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, node_graph_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, @@ -2097,6 +2099,7 @@ impl NodeNetworkInterface { log::error!("Could not get current network in load_export_ports"); return; }; + log::debug!("Setting rounded network edge distance to {network_edge_distance:?}"); network_metadata.transient_metadata.rounded_network_edge_distance = TransientMetadata::Loaded(network_edge_distance); } @@ -2156,7 +2159,6 @@ impl NodeNetworkInterface { .unwrap_or([DVec2::new(0., 0.), DVec2::new(0., 0.)]); let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; - network_metadata.transient_metadata.all_nodes_bounding_box = TransientMetadata::Loaded(all_nodes_bounding_box); } @@ -5718,6 +5720,33 @@ impl PartialEq for DocumentNodeMetadata { } } +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum WidgetOverride { + Vector2(Vector2Override), + Text(TextOverride), +} + +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PropertiesRow { + /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. + /// If the string is empty/DNE, then it uses the type. + pub input_name: String, + /// Hide the properties row for this input for all types + pub hidden: bool, + // An input can override multiple widgets for different types + pub widget_override: Vec, +} + +impl From for PropertiesRow { + fn from(name: String) -> Self { + PropertiesRow { + name, + hidden: false, + widget_override: Vec::new(), + } + } +} + /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DocumentNodePersistentMetadata { @@ -5727,9 +5756,10 @@ pub struct DocumentNodePersistentMetadata { /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics. #[serde(default)] pub display_name: String, - /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. - /// If the string is empty/DNE, then it uses the type. - pub input_names: Vec, + /// Stores metadata to display custom properties in the properties panel for each input, which are generated automatically based on the type + pub input_properties: Vec, + /// A node can have a fully custom properties panel. For example to display a single string, or if interdependent properties are needed + pub custom_properties: Option<&'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync)>, pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. @@ -5753,7 +5783,8 @@ impl Default for DocumentNodePersistentMetadata { DocumentNodePersistentMetadata { reference: None, display_name: String::new(), - input_names: Vec::new(), + input_properties: Vec::new(), + custom_properties: None, output_names: Vec::new(), has_primary_output: true, pinned: false, diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index c6c3328fab..04c5ec6e13 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -29,6 +29,7 @@ pub mod types { pub type Resolution = glam::UVec2; } +// Translation struct between macro and definition #[derive(Clone)] pub struct NodeMetadata { pub display_name: &'static str, @@ -37,6 +38,7 @@ pub struct NodeMetadata { pub description: &'static str, } +// Translation struct between macro and definition #[derive(Clone, Debug)] pub struct FieldMetadata { pub name: &'static str, From 1db77289c9a70dd081823d458ad36a19d9c9b537 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 17 Nov 2024 23:58:02 -0800 Subject: [PATCH 03/19] WIP: Input based properties --- .../node_graph/document_node_definitions.rs | 199 ++++++++---------- .../document/node_graph/node_properties.rs | 32 +-- .../utility_types/network_interface.rs | 114 +++++++--- 3 files changed, 189 insertions(+), 156 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 6f8983665d..f9adcf55dc 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 @@ -3,6 +3,7 @@ use super::utility_types::FrontendNodeType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::network_interface::{ DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, PropertiesRow, + WidgetOverride, }; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::Message; @@ -88,8 +89,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["In".to_string().into()], - custom_properties: &|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through"), + input_properties: vec![PropertiesRow::with_override("In", WidgetOverride::string("The identity node simply passes its data through."))], output_names: vec!["Out".to_string()], ..Default::default() }, @@ -110,8 +110,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["In".to_string().into()], - custom_properties: &|_node_id, _context| node_properties::string_properties("The Monitor node is used by the editor to access the data flowing through it."), + input_properties: vec![PropertiesRow::with_override( + "In", + WidgetOverride::string("The Monitor node is used by the editor to access the data flowing through it."), + )], output_names: vec!["Out".to_string()], ..Default::default() }, @@ -172,7 +174,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Graphical Data".to_string(), "Over".to_string()], + input_properties: vec!["Graphical Data".into(), "Over".into()], output_names: vec!["Out".to_string()], node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)), network_metadata: Some(NodeNetworkMetadata { @@ -283,14 +285,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec![ - "Artboards".to_string(), - "Contents".to_string(), - "Location".to_string(), - "Dimensions".to_string(), - "Background".to_string(), - "Clip".to_string(), - ], + input_properties: vec!["Artboards".into(), "Contents".into(), "Location".into(), "Dimensions".into(), "Background".into(), "Clip".into()], output_names: vec!["Out".to_string()], node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)), network_metadata: Some(NodeNetworkMetadata { @@ -371,7 +366,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["api".to_string(), "path".to_string()], + input_properties: vec!["api".into(), "path".into()], output_names: vec!["Image Frame".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -519,7 +514,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Canvas".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -618,7 +613,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Artwork".to_string(), "Footprint".to_string()], + input_properties: vec!["Artwork".into(), "Footprint".into()], output_names: vec!["Canvas".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -694,8 +689,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".to_string().into(), "Transform".to_string().into()], - custom_properties: Some(&|_node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform")), + input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::string("Creates an embedded image with the given transform."))], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -757,22 +751,22 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec![ - "Clip".to_string(), - "Seed".to_string(), - "Scale".to_string(), - "Noise Type".to_string(), - "Domain Warp Type".to_string(), - "Domain Warp Amplitude".to_string(), - "Fractal Type".to_string(), - "Fractal Octaves".to_string(), - "Fractal Lacunarity".to_string(), - "Fractal Gain".to_string(), - "Fractal Weighted Strength".to_string(), - "Fractal Ping Pong Strength".to_string(), - "Cellular Distance Function".to_string(), - "Cellular Return Type".to_string(), - "Cellular Jitter".to_string(), + input_properties: vec![ + "Clip".into(), + "Seed".into(), + "Scale".into(), + "Noise Type".into(), + "Domain Warp Type".into(), + "Domain Warp Amplitude".into(), + "Fractal Type".into(), + "Fractal Octaves".into(), + "Fractal Lacunarity".into(), + "Fractal Gain".into(), + "Fractal Weighted Strength".into(), + "Fractal Ping Pong Strength".into(), + "Cellular Distance Function".into(), + "Cellular Return Type".into(), + "Cellular Jitter".into(), ], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { @@ -823,7 +817,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Stencil".to_string()], + input_properties: vec!["Image".into(), "Stencil".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -846,7 +840,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Insertion".to_string(), "Replace".to_string()], + input_properties: vec!["Image".into(), "Insertion".into(), "Replace".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -870,7 +864,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["None".to_string(), "Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()], + input_properties: vec!["None".into(), "Red".into(), "Green".into(), "Blue".into(), "Alpha".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -938,7 +932,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string()], + input_properties: vec!["Image".into()], output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()], has_primary_output: false, network_metadata: Some(NodeNetworkMetadata { @@ -1023,7 +1017,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Background".to_string(), "Bounds".to_string(), "Trace".to_string(), "Cache".to_string()], + input_properties: vec!["Background".into(), "Bounds".into(), "Trace".into(), "Cache".into()], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1069,7 +1063,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string()], + input_properties: vec!["Image".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1087,7 +1081,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string()], + input_properties: vec!["Image".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1117,8 +1111,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".to_string()], - custom_properties: Some(&|_node_id, _context| node_properties::string_properties("A bitmap image is embedded in this node")), + input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::string("A bitmap image is embedded in this node"))], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1180,7 +1173,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Uniform".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1258,7 +1251,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Storage".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1336,7 +1329,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string(), "In".to_string()], + input_properties: vec!["In".into(), "In".into()], output_names: vec!["Output Buffer".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1424,7 +1417,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string(), "In".to_string(), "In".to_string()], + input_properties: vec!["In".into(), "In".into(), "In".into()], output_names: vec!["Command Buffer".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1483,7 +1476,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Shader Handle".to_string(), "String".to_string(), "Bindgroup".to_string(), "Arc Shader Input".to_string()], + input_properties: vec!["Shader Handle".into(), "String".into(), "Bindgroup".into(), "Arc Shader Input".into()], output_names: vec!["Pipeline Layout".to_string()], ..Default::default() }, @@ -1527,7 +1520,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Pipeline Result".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1606,7 +1599,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Buffer".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1749,7 +1742,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Texture".to_string(), "Surface".to_string()], + input_properties: vec!["Texture".into(), "Surface".into()], output_names: vec!["Rendered Texture".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1821,7 +1814,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["In".to_string()], + input_properties: vec!["In".into()], output_names: vec!["Texture".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1878,7 +1871,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Node".to_string()], + input_properties: vec!["Image".into(), "Node".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1895,7 +1888,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Node".to_string()], + input_properties: vec!["Node".into()], output_names: vec!["Document Node".to_string()], ..Default::default() }, @@ -1920,7 +1913,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Brightness".to_string(), "Contrast".to_string(), "Use Classic".to_string()], + input_properties: vec!["Image".into(), "Brightness".into(), "Contrast".into(), "Use Classic".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1943,7 +1936,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Image".to_string(), "Curve".to_string()], + input_properties: vec!["Image".into(), "Curve".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1966,7 +1959,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["None".to_string(), "Start".to_string(), "End".to_string()], + input_properties: vec!["None".into(), "Start".into(), "End".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -1988,7 +1981,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["None".to_string(), "Points".to_string()], + input_properties: vec!["None".into(), "Points".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2030,7 +2023,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Vector Data".to_string(), "Modification".to_string()], + input_properties: vec!["Vector Data".into(), "Modification".into()], output_names: vec!["Vector Data".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -2086,14 +2079,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec![ - "Editor API".to_string(), - "Text".to_string(), - "Font".to_string(), - "Size".to_string(), - "Line Height".to_string(), - "Character Spacing".to_string(), - ], + input_properties: vec!["Editor API".into(), "Text".into(), "Font".into(), "Size".into(), "Line Height".into(), "Character Spacing".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2172,14 +2158,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_names: vec![ - "Vector Data".to_string(), - "Translation".to_string(), - "Rotation".to_string(), - "Scale".to_string(), - "Skew".to_string(), - "Pivot".to_string(), - ], + input_properties: vec!["Vector Data".into(), "Translation".into(), "Rotation".into(), "Scale".into(), "Skew".into(), "Pivot".into()], output_names: vec!["Data".to_string()], ..Default::default() }, @@ -2249,7 +2228,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_names: vec!["Group of Paths".to_string(), "Operation".to_string()], + input_properties: vec!["Group of Paths".into(), "Operation".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2278,15 +2257,15 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec![ - "Points".to_string(), - "Instance".to_string(), - "Random Scale Min".to_string(), - "Random Scale Max".to_string(), - "Random Scale Bias".to_string(), - "Random Scale Seed".to_string(), - "Random Rotation".to_string(), - "Random Rotation Seed".to_string(), + input_properties: vec![ + "Points".into(), + "Instance".into(), + "Random Scale Min".into(), + "Random Scale Max".into(), + "Random Scale Bias".into(), + "Random Scale Seed".into(), + "Random Rotation".into(), + "Random Rotation Seed".into(), ], output_names: vec!["Vector".to_string()], ..Default::default() @@ -2378,13 +2357,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_names: vec![ - "Vector Data".to_string(), - "Spacing".to_string(), - "Start Offset".to_string(), - "Stop Offset".to_string(), - "Adaptive Spacing".to_string(), - ], + input_properties: vec!["Vector Data".into(), "Spacing".into(), "Start Offset".into(), "Stop Offset".into(), "Adaptive Spacing".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2457,7 +2430,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_names: vec!["Vector Data".to_string(), "Separation Disk Diameter".to_string(), "Seed".to_string()], + input_properties: vec!["Vector Data".into(), "Separation Disk Diameter".into(), "Seed".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2476,7 +2449,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_names: vec!["Segmentation".to_string(), "Index".to_string()], + input_properties: vec!["Segmentation".into(), "Index".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -2563,7 +2536,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: fields.iter().map(|f| f.name.to_string().into()).collect(), + input_properties: fields.iter().map(|f| f.name.into()).collect(), output_names: vec![output_type.to_string()], has_primary_output: true, locked: false, @@ -2673,24 +2646,24 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN }, ..Default::default() }), - input_names: vec![ - "Input Image".to_string(), - "Editor Api".to_string(), - "Controller".to_string(), - "Seed".to_string(), - "Resolution".to_string(), - "Samples".to_string(), - "Sampling Method".to_string(), - "Prompt Guidance".to_string(), - "Prompt".to_string(), - "Negative Prompt".to_string(), - "Adapt Input Image".to_string(), - "Image Creativity".to_string(), - "Inpaint".to_string(), - "Mask Blur".to_string(), - "Mask Starting Fill".to_string(), - "Improve Faces".to_string(), - "Tiling".to_string(), + input_properties: vec![ + "Input Image".into(), + "Editor Api".into(), + "Controller".into(), + "Seed".into(), + "Resolution".into(), + "Samples".into(), + "Sampling Method".into(), + "Prompt Guidance".into(), + "Prompt".into(), + "Negative Prompt".into(), + "Adapt Input Image".into(), + "Image Creativity".into(), + "Inpaint".into(), + "Mask Blur".into(), + "Mask Starting Fill".into(), + "Improve Faces".into(), + "Tiling".into(), ], output_names: vec!["Image".to_string()], ..Default::default() 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 b2a65224e8..b2845dd2c9 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -3,7 +3,7 @@ use super::document_node_definitions::{NodePropertiesContext, IMAGINATE_NODE}; use super::utility_types::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; +use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, WidgetOverride}; use crate::messages::prelude::*; use graph_craft::document::value::TaggedValue; @@ -94,11 +94,6 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, conte return vec![]; }; - // Early return if input is hidden - if input_properties_row.hidden { - return vec![]; - } - let name = &input_properties_row.input_name; let Some(network) = context.network_interface.network(context.selection_network_path) else { @@ -1767,9 +1762,9 @@ pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId IMAGINATE_NODE .default_node_template() .persistent_node_metadata - .input_names + .input_properties .iter() - .position(|input| input == name) + .position(|row| row.input_name == name) .unwrap_or_else(|| panic!("Input {name} not found")) }; let seed_index = resolve_input("Seed"); @@ -2271,18 +2266,23 @@ pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _c pub(crate) fn generate_node_properties(node_id: NodeId, pinned: bool, context: &mut NodePropertiesContext) -> LayoutGroup { let mut layout = Vec::new(); - if let Some(custom_properties) = context.network_interface.custom_properties(&node_id, context.selection_network_path) { - layout = custom_properties(node_id, context); - } else { - let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); - for input_index in 0..number_of_inputs { + let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); + for input_index in 0..number_of_inputs { + if let Some(widget_override) = context.network_interface.widget_override(&node_id, input_index, context.selection_network_path) { + let mut widget_override = std::mem::replace(&mut WidgetOverride(Box::new())); + layout.extend(widget_override.0(node_id, context)); + let empty_widget_override = context + .network_interface + .widget_override(&node_id, input_index, context.selection_network_path) + .replace(&mut widget_override); + } else { let input_type = context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path); let row = property_from_type(node_id, input_index, &input_type.0, context); layout.extend(row); } - if layout.is_empty() { - layout = node_no_properties(node_id, context); - } + } + if layout.is_empty() { + layout = node_no_properties(node_id, context); } let name = context.network_interface.reference(&node_id, context.selection_network_path).clone().unwrap_or_default(); let visible = context.network_interface.is_visible(&node_id, context.selection_network_path); 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 5bd360b2f4..fcfad70f3d 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3,7 +3,8 @@ use super::misc::PTZ; use super::nodes::SelectedNodes; use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; -use crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_document_node_type, DocumentNodeDefinition, NodePropertiesContext}; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type, DocumentNodeDefinition, NodePropertiesContext}; +use crate::messages::portfolio::document::node_graph::node_properties; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::LayoutGroup; @@ -717,9 +718,9 @@ impl NodeNetworkInterface { .filter_map(|(import_index, click_target)| { // Get import name from parent node metadata input, which must match the number of imports. // Empty string means to use type, or "Import + index" if type can't be determined - let import_name = self + let properties_row = self .encapsulating_node_metadata(network_path) - .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.input_names.get(*import_index).cloned()) + .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.input_properties.get(*import_index).cloned()) .unwrap_or_default(); let mut import_metadata = None; @@ -731,7 +732,11 @@ impl NodeNetworkInterface { let (input_type, type_source) = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); let data_type = FrontendGraphDataType::with_type(&input_type); - let import_name = if import_name.is_empty() { input_type.clone().nested_type().to_string() } else { import_name }; + let import_name = if properties_row.input_name.is_empty() { + input_type.clone().nested_type().to_string() + } else { + properties_row.input_name + }; let connected_to = self .outward_wires(network_path) @@ -1042,10 +1047,16 @@ impl NodeNetworkInterface { .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index)) } - pub fn custom_properties(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync)> { - self.node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.custom_properties) + pub fn input_properties_row_mut(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&mut PropertiesRow> { + self.node_metadata_mut(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get_mut(index)) + } + + pub fn widget_override(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&mut WidgetOverride> { + self.input_properties_row_mut(node_id, index, network_path) + .and_then(|node_metadata| node_metadata.widget_override.as_mut()) } + // Use frontend display name instead fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { @@ -3290,9 +3301,9 @@ impl NodeNetworkInterface { return; }; if insert_index == -1 { - node_metadata.persistent_metadata.input_names.push(input_name); + node_metadata.persistent_metadata.input_properties.push(input_name.into()); } else { - node_metadata.persistent_metadata.input_names.insert(insert_index as usize, input_name); + node_metadata.persistent_metadata.input_properties.insert(insert_index as usize, input_name.into()); } // Update the metadata for the encapsulating node @@ -3406,7 +3417,7 @@ impl NodeNetworkInterface { log::error!("Could not get encapsulating node metadata in remove_export"); return; }; - encapsulating_node_metadata.persistent_metadata.input_names.remove(import_index); + encapsulating_node_metadata.persistent_metadata.input_properties.remove(import_index); // Update the metadata for the encapsulating node self.unload_outward_wires(&encapsulating_network_path); @@ -5720,29 +5731,80 @@ impl PartialEq for DocumentNodeMetadata { } } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] -pub enum WidgetOverride { - Vector2(Vector2Override), - Text(TextOverride), +pub trait CloneableFn: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Send + Sync { + fn clone_box(&self) -> Box; } -#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +impl CloneableFn for T +where + T: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Clone + Send + Sync + 'static, +{ + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +use std::fmt; + +// TODO: Store the lambdas as enums which can then be mapped into lambdas when serializing/deserializing/cloning +pub trait CloneableFn: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Send + Sync { + fn clone_box(&self) -> Box; +} + +impl fmt::Debug for dyn CloneableFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CloneableFn") + } +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct WidgetOverride(pub Box); + +impl Clone for WidgetOverride { + fn clone(&self) -> Self { + WidgetOverride(self.0.clone_box()) + } +} + +impl WidgetOverride { + pub fn hidden() -> Self { + WidgetOverride(Box::new(|_node_id: NodeId, _context| Vec::new())) + } + + pub fn string(string: &'static str) -> Self { + WidgetOverride(Box::new(|_node_id: NodeId, _context| node_properties::string_properties(string))) + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. /// If the string is empty/DNE, then it uses the type. pub input_name: String, - /// Hide the properties row for this input for all types - pub hidden: bool, - // An input can override multiple widgets for different types - pub widget_override: Vec, + // An input can override a widget, which would otherwise be automatically generated from the type + pub widget_override: Option, +} + +impl PartialEq for PropertiesRow { + fn eq(&self, other: &Self) -> bool { + self.input_name == other.input_name + } +} + +impl From<&str> for PropertiesRow { + fn from(input_name: &str) -> Self { + PropertiesRow { + input_name: input_name.to_string(), + widget_override: None, + } + } } -impl From for PropertiesRow { - fn from(name: String) -> Self { +impl PropertiesRow { + pub fn with_override(input_name: &str, widget_override: WidgetOverride) -> Self { PropertiesRow { - name, - hidden: false, - widget_override: Vec::new(), + input_name: input_name.to_string(), + widget_override: Some(widget_override), } } } @@ -5756,10 +5818,9 @@ pub struct DocumentNodePersistentMetadata { /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics. #[serde(default)] pub display_name: String, - /// Stores metadata to display custom properties in the properties panel for each input, which are generated automatically based on the type + /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. pub input_properties: Vec, /// A node can have a fully custom properties panel. For example to display a single string, or if interdependent properties are needed - pub custom_properties: Option<&'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync)>, pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. @@ -5784,7 +5845,6 @@ impl Default for DocumentNodePersistentMetadata { reference: None, display_name: String::new(), input_properties: Vec::new(), - custom_properties: None, output_names: Vec::new(), has_primary_output: true, pinned: false, From 228dedd32b97d7fc2b0024b02a791d8d2f326e61 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 22 Nov 2024 23:51:18 -0800 Subject: [PATCH 04/19] WIP: Hashmap based input overrides --- .../document/document_message_handler.rs | 2 +- .../node_graph/document_node_definitions.rs | 100 +++++++- .../node_graph/node_graph_message_handler.rs | 11 +- .../document/node_graph/node_properties.rs | 241 +++++++++--------- .../utility_types/network_interface.rs | 136 +++++----- frontend/wasm/src/editor_api.rs | 2 +- 6 files changed, 279 insertions(+), 213 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index c1f11376f5..74482810f6 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -2067,7 +2067,7 @@ impl DocumentMessageHandler { /// Create a network interface with a single export fn default_document_network_interface() -> NodeNetworkInterface { let mut network_interface = NodeNetworkInterface::default(); - network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "".to_string(), &[]); + network_interface.add_export(TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY), -1, "", &[]); network_interface } 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 f9adcf55dc..06edab8921 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 @@ -38,6 +38,30 @@ pub struct NodePropertiesContext<'a> { pub document_name: &'a str, } +impl NodePropertiesContext<'_> { + pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option> { + //let current_override = //Get mutable reference from transient metadata + //let mut widget_override = std::mem::replace(&mut WidgetOverrideLambda(Box::new()), current_override); + // let layout = widget_override.0(node_id, context); + //let current_override = //Get mutable reference from transient metadata (now empty) + //let empty_widget_override = std::mem::replace(&mut widget_override, current_override) // Put back the taken override + // Some(layout) + let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { + log::error!("Could not get input properties row in call_widget_override"); + return None; + }; + let Some(widget_override_lambda) = input_properties_row.widget_override.as_ref().and_then(|widget_override| INPUT_OVERRIDES.get(widget_override)) else { + log::error!("Could not get widget override lambda in call_widget_override"); + return None; + }; + widget_override_lambda(*node_id, index, self) + .map_err(|error| { + log::error!("Error in widget override lambda: {}", error); + }) + .ok() + } +} + /// Acts as a description for a [DocumentNode] before it gets instantiated as one. #[derive(Clone)] pub struct DocumentNodeDefinition { @@ -89,7 +113,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![PropertiesRow::with_override("In", WidgetOverride::string("The identity node simply passes its data through."))], + input_properties: vec![PropertiesRow::with_override( + "In", + WidgetOverride::String("The identity node simply passes its data through.".to_string()), + )], output_names: vec!["Out".to_string()], ..Default::default() }, @@ -112,7 +139,7 @@ fn static_nodes() -> Vec { persistent_node_metadata: DocumentNodePersistentMetadata { input_properties: vec![PropertiesRow::with_override( "In", - WidgetOverride::string("The Monitor node is used by the editor to access the data flowing through it."), + WidgetOverride::String("The Monitor node is used by the editor to access the data flowing through it.".to_string()), )], output_names: vec!["Out".to_string()], ..Default::default() @@ -285,7 +312,14 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Artboards".into(), "Contents".into(), "Location".into(), "Dimensions".into(), "Background".into(), "Clip".into()], + input_properties: vec![ + PropertiesRow::with_override("Artboards", WidgetOverride::Hidden), + PropertiesRow::with_override("Contents", WidgetOverride::Hidden), + "Location".into(), + "Dimensions".into(), + "Background".into(), + "Clip".into(), + ], output_names: vec!["Out".to_string()], node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)), network_metadata: Some(NodeNetworkMetadata { @@ -366,7 +400,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["api".into(), "path".into()], + input_properties: vec![PropertiesRow::with_override("api", WidgetOverride::Hidden), "path".into()], output_names: vec!["Image Frame".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -689,7 +723,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::string("Creates an embedded image with the given transform."))], + input_properties: vec![PropertiesRow::with_override( + "Image", + WidgetOverride::String("Creates an embedded image with the given transform.".to_string()), + )], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -754,7 +791,7 @@ fn static_nodes() -> Vec { input_properties: vec![ "Clip".into(), "Seed".into(), - "Scale".into(), + PropertiesRow::with_override("Scale", WidgetOverride::Custom("noise_properties_scale".to_string())), "Noise Type".into(), "Domain Warp Type".into(), "Domain Warp Amplitude".into(), @@ -817,7 +854,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".into(), "Stencil".into()], + input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::Hidden), "Stencil".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -840,7 +877,11 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".into(), "Insertion".into(), "Replace".into()], + input_properties: vec![ + PropertiesRow::with_override("Image", WidgetOverride::Hidden), + PropertiesRow::with_override("Insertion", WidgetOverride::Hidden), + "Replace".into(), + ], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1111,7 +1152,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::string("A bitmap image is embedded in this node"))], + input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::String("A bitmap image is embedded in this node".to_string()))], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -2673,6 +2714,47 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN description: Cow::Borrowed("TODO"), }); +static INPUT_OVERRIDES: once_cell::sync::Lazy Result, String> + Send + Sync>>> = + once_cell::sync::Lazy::new(static_input_properties); + +/// Defines the logic for inputs to display a custom properties panel widget. +fn static_input_properties() -> HashMap Result, String> + Send + Sync>> { + let mut map: HashMap Result, String> + Send + Sync>> = HashMap::new(); + map.insert("hidden".to_string(), Box::new(|_node_id, _index, _context| Ok(Vec::new()))); + map.insert( + "string".to_string(), + Box::new(|node_id, index, context| { + let Some(value) = context.network_interface.input_metadata(&node_id, index, "string_properties", context.selection_network_path) else { + return Err(format!("Could not get string properties for node {}", node_id)); + }; + let Some(string) = value.as_str() else { + return Err(format!("Could not downcast string properties for node {}", node_id)); + }; + Ok(node_properties::string_properties(string.to_string())) + }), + ); + map.insert( + "noise_properties_scale".to_string(), + Box::new(|node_id, index, context| { + let network = context.network_interface.network(context.selection_network_path).ok_or("network not found in noise_properties_scale")?; + let document_node = network.nodes.get(&node_id).ok_or("node not found in noise_properties_scale")?; + let current_noise_type = document_node.inputs.iter().find_map(|input| match input.as_value() { + Some(&TaggedValue::NoiseType(noise_type)) => Some(noise_type), + _ => None, + }); + let coherent_noise_active = current_noise_type != Some(NoiseType::WhiteNoise); + let input_name = context + .network_interface + .input_name(&node_id, index, context.selection_network_path) + .ok_or("input name not found in noise_properties_scale")?; + + let scale = node_properties::number_widget(document_node, node_id, 2, input_name, NumberInput::default().min(0.).disabled(!coherent_noise_active), true); + Ok(vec![scale.into()]) + }), + ); + map +} + pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefinition> { DOCUMENT_NODE_TYPES.iter().find(|definition| definition.identifier == identifier) } 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 a2c91ce7e0..204eb37bef 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 @@ -9,7 +9,7 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{ - self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, PropertiesRow, TypeSource + self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::prelude::*; @@ -100,11 +100,11 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] }); } NodeGraphMessage::AddImport => { - network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, String::new(), breadcrumb_network_path); + network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", breadcrumb_network_path); responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::AddExport => { - network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, String::new(), breadcrumb_network_path); + network_interface.add_export(graph_craft::document::value::TaggedValue::None, -1, "", breadcrumb_network_path); responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::Init => { @@ -2448,7 +2448,10 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: } // Get the name from the metadata here (since it also requires a reference to the `network_interface`) - let name = network_interface.input_properties_row(&node_id, index, breadcrumb_network_path).cloned().map(|properties_row|properties_row.input_name).filter(|s| !s.is_empty()); + let name = network_interface + .input_name(&node_id, index, breadcrumb_network_path) + .filter(|s| !s.is_empty()) + .map(|name| name.to_string()); // Get the output connector that feeds into this input (done here as well for simplicity) let connector = OutputConnector::from_input(input); 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 b2845dd2c9..26489b5c54 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -3,7 +3,7 @@ use super::document_node_definitions::{NodePropertiesContext, IMAGINATE_NODE}; use super::utility_types::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, WidgetOverride}; +use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; use crate::messages::prelude::*; use graph_craft::document::value::TaggedValue; @@ -26,7 +26,7 @@ use graphene_std::transform::Footprint; use graphene_std::vector::misc::BooleanOperation; use graphene_std::vector::VectorData; -pub(crate) fn string_properties(text: impl Into) -> Vec { +pub(crate) fn string_properties(text: String) -> Vec { let widget = TextLabel::new(text).widget_holder(); vec![LayoutGroup::Row { widgets: vec![widget] }] } @@ -89,13 +89,11 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na } pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, context: &mut NodePropertiesContext) -> Vec { - let Some(input_properties_row) = context.network_interface.input_properties_row(&node_id, index, context.selection_network_path) else { - log::warn!("A widget failed to be built for node {node_id}, index {index} because the input connector could not be determined"); + let Some(name) = context.network_interface.input_name(&node_id, index, context.selection_network_path) else { + log::warn!("A widget failed to be built for node {node_id}, index {index} because the input name could not be determined"); return vec![]; }; - let name = &input_properties_row.input_name; - let Some(network) = context.network_interface.network(context.selection_network_path) else { log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined"); return vec![]; @@ -134,109 +132,109 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, conte // For all other types, use TypeId-based matching _ => { - if let Some(internal_id) = concrete_type.id { - use std::any::TypeId; - match internal_id { - x if x == TypeId::of::() => bool_widget(document_node, node_id, index, name, CheckboxInput::default(), true).into(), - x if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY)), true).into(), - x if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX))), true).into(), - x if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(), - x if x == TypeId::of::() => text_widget(document_node, node_id, index, name, true).into(), - x if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), - x if x == TypeId::of::>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true), - x if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), - x if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist), - x if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), - x if x == TypeId::of::>() => vec_f64_input(document_node, node_id, index, name, TextInput::default(), true).into(), - x if x == TypeId::of::>() => vec_dvec2_input(document_node, node_id, index, name, TextInput::default(), true).into(), - x if x == TypeId::of::() => { - let (font_widgets, style_widgets) = font_inputs(document_node, node_id, index, name, false); - font_widgets.into_iter().chain(style_widgets.unwrap_or_default()).collect::>().into() - } - x if x == TypeId::of::() => curves_widget(document_node, node_id, index, name, true), - x if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), - x if x == TypeId::of::() => vector_widget(document_node, node_id, index, name, true).into(), - x if x == TypeId::of::() => { - let widgets = footprint_widget(document_node, node_id, index); - let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows"); - extra_widgets = rest.to_vec(); - last.clone() - } - x if x == TypeId::of::() => blend_mode(document_node, node_id, index, name, true), - x if x == TypeId::of::() => color_channel(document_node, node_id, index, name, true), - x if x == TypeId::of::() => rgba_channel(document_node, node_id, index, name, true), - x if x == TypeId::of::() => noise_type(document_node, node_id, index, name, true), - x if x == TypeId::of::() => fractal_type(document_node, node_id, index, name, true, false), - x if x == TypeId::of::() => cellular_distance_function(document_node, node_id, index, name, true, false), - x if x == TypeId::of::() => cellular_return_type(document_node, node_id, index, name, true, false), - x if x == TypeId::of::() => domain_warp_type(document_node, node_id, index, name, true, false), - x if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ - MenuListEntry::new("Relative") - .label("Relative") - .on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), node_id, index)), - MenuListEntry::new("Absolute") - .label("Absolute") - .on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Absolute), node_id, index)), - ]]) - .widget_holder()] - .into(), - x if x == TypeId::of::() => line_cap_widget(document_node, node_id, index, name, true), - x if x == TypeId::of::() => line_join_widget(document_node, node_id, index, name, true), - x if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ - MenuListEntry::new("Solid") - .label("Solid") - .on_update(update_value(|_| TaggedValue::FillType(FillType::Solid), node_id, index)), - MenuListEntry::new("Gradient") - .label("Gradient") - .on_update(update_value(|_| TaggedValue::FillType(FillType::Gradient), node_id, index)), - ]]) - .widget_holder()] - .into(), - x if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ - MenuListEntry::new("Linear") - .label("Linear") - .on_update(update_value(|_| TaggedValue::GradientType(GradientType::Linear), node_id, index)), - MenuListEntry::new("Radial") - .label("Radial") - .on_update(update_value(|_| TaggedValue::GradientType(GradientType::Radial), node_id, index)), - ]]) - .widget_holder()] - .into(), - x if x == TypeId::of::() => boolean_operation_radio_buttons(document_node, node_id, index, name, true), - x if x == TypeId::of::() => centroid_widget(document_node, node_id, index), - x if x == TypeId::of::() => luminance_calculation(document_node, node_id, index, name, true), - x if x == TypeId::of::() => vec![DropdownInput::new( - ImaginateSamplingMethod::list() - .into_iter() - .map(|method| { - vec![MenuListEntry::new(format!("{:?}", method)).label(method.to_string()).on_update(update_value( - move |_| TaggedValue::ImaginateSamplingMethod(method), - node_id, - index, - ))] - }) - .collect(), - ) - .widget_holder()] - .into(), - x if x == TypeId::of::() => vec![DropdownInput::new( - ImaginateMaskStartingFill::list() - .into_iter() - .map(|fill| { - vec![MenuListEntry::new(format!("{:?}", fill)).label(fill.to_string()).on_update(update_value( - move |_| TaggedValue::ImaginateMaskStartingFill(fill), - node_id, - index, - ))] - }) - .collect(), - ) - .widget_holder()] - .into(), - _ => vec![TextLabel::new(format!("Unsupported type: {}", concrete_type.name)).widget_holder()].into(), + use std::any::TypeId; + match concrete_type.id { + Some(x) if x == TypeId::of::() => bool_widget(document_node, node_id, index, name, CheckboxInput::default(), true).into(), + Some(x) if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY)), true).into(), + Some(x) if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX))), true).into(), + Some(x) if x == TypeId::of::() => number_widget(document_node, node_id, index, name, number_input.int().min(min(0.)), true).into(), + Some(x) if x == TypeId::of::() => text_widget(document_node, node_id, index, name, true).into(), + Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), + Some(x) if x == TypeId::of::>() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(true), true), + Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), + Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", Some(0.), add_blank_assist), + Some(x) if x == TypeId::of::() => vec2_widget(document_node, node_id, index, name, "X", "Y", "", None, add_blank_assist), + Some(x) if x == TypeId::of::>() => vec_f64_input(document_node, node_id, index, name, TextInput::default(), true).into(), + Some(x) if x == TypeId::of::>() => vec_dvec2_input(document_node, node_id, index, name, TextInput::default(), true).into(), + Some(x) if x == TypeId::of::() => { + let (font_widgets, style_widgets) = font_inputs(document_node, node_id, index, name, false); + font_widgets.into_iter().chain(style_widgets.unwrap_or_default()).collect::>().into() + } + Some(x) if x == TypeId::of::() => curves_widget(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => color_widget(document_node, node_id, index, name, ColorButton::default().allow_none(false), true), + Some(x) if x == TypeId::of::() => vector_widget(document_node, node_id, index, name, true).into(), + Some(x) if x == TypeId::of::() => { + let widgets = footprint_widget(document_node, node_id, index); + let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows"); + extra_widgets = rest.to_vec(); + last.clone() + } + Some(x) if x == TypeId::of::() => blend_mode(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => color_channel(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => rgba_channel(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => noise_type(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => fractal_type(document_node, node_id, index, name, true, false), + Some(x) if x == TypeId::of::() => cellular_distance_function(document_node, node_id, index, name, true, false), + Some(x) if x == TypeId::of::() => cellular_return_type(document_node, node_id, index, name, true, false), + Some(x) if x == TypeId::of::() => domain_warp_type(document_node, node_id, index, name, true, false), + Some(x) if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ + MenuListEntry::new("Relative") + .label("Relative") + .on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Relative), node_id, index)), + MenuListEntry::new("Absolute") + .label("Absolute") + .on_update(update_value(|_| TaggedValue::RelativeAbsolute(RelativeAbsolute::Absolute), node_id, index)), + ]]) + .widget_holder()] + .into(), + Some(x) if x == TypeId::of::() => line_cap_widget(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => line_join_widget(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ + MenuListEntry::new("Solid") + .label("Solid") + .on_update(update_value(|_| TaggedValue::FillType(FillType::Solid), node_id, index)), + MenuListEntry::new("Gradient") + .label("Gradient") + .on_update(update_value(|_| TaggedValue::FillType(FillType::Gradient), node_id, index)), + ]]) + .widget_holder()] + .into(), + Some(x) if x == TypeId::of::() => vec![DropdownInput::new(vec![vec![ + MenuListEntry::new("Linear") + .label("Linear") + .on_update(update_value(|_| TaggedValue::GradientType(GradientType::Linear), node_id, index)), + MenuListEntry::new("Radial") + .label("Radial") + .on_update(update_value(|_| TaggedValue::GradientType(GradientType::Radial), node_id, index)), + ]]) + .widget_holder()] + .into(), + Some(x) if x == TypeId::of::() => boolean_operation_radio_buttons(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => centroid_widget(document_node, node_id, index), + Some(x) if x == TypeId::of::() => luminance_calculation(document_node, node_id, index, name, true), + Some(x) if x == TypeId::of::() => vec![DropdownInput::new( + ImaginateSamplingMethod::list() + .into_iter() + .map(|method| { + vec![MenuListEntry::new(format!("{:?}", method)).label(method.to_string()).on_update(update_value( + move |_| TaggedValue::ImaginateSamplingMethod(method), + node_id, + index, + ))] + }) + .collect(), + ) + .widget_holder()] + .into(), + Some(x) if x == TypeId::of::() => vec![DropdownInput::new( + ImaginateMaskStartingFill::list() + .into_iter() + .map(|fill| { + vec![MenuListEntry::new(format!("{:?}", fill)).label(fill.to_string()).on_update(update_value( + move |_| TaggedValue::ImaginateMaskStartingFill(fill), + node_id, + index, + ))] + }) + .collect(), + ) + .widget_holder()] + .into(), + _ => { + let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, false); + widgets.extend_from_slice(&[TextLabel::new(format!("Unsupported type: {}", concrete_type.name)).widget_holder()]); + widgets.into() } - } else { - vec![TextLabel::new(format!("Unsupported type: {}", concrete_type.name)).widget_holder()].into() } } } @@ -639,7 +637,7 @@ fn vector_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na widgets } -fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, number_props: NumberInput, blank_assist: bool) -> Vec { +pub fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, number_props: NumberInput, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -1764,7 +1762,7 @@ pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId .persistent_node_metadata .input_properties .iter() - .position(|row| row.input_name == name) + .position(|row| row.input_data.get("input_name").and_then(|v| v.as_str()) == Some(name)) .unwrap_or_else(|| panic!("Input {name} not found")) }; let seed_index = resolve_input("Seed"); @@ -2245,15 +2243,11 @@ pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId layout } -fn unknown_node_properties(reference: &String) -> Vec { - string_properties(format!("Node '{}' cannot be found in library", reference)) -} - pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { string_properties(if context.network_interface.is_layer(&node_id, context.selection_network_path) { - "Layer has no properties" + "Layer has no properties".to_string() } else { - "Node has no properties" + "Node has no properties".to_string() }) } @@ -2268,18 +2262,11 @@ pub(crate) fn generate_node_properties(node_id: NodeId, pinned: bool, context: & let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); for input_index in 0..number_of_inputs { - if let Some(widget_override) = context.network_interface.widget_override(&node_id, input_index, context.selection_network_path) { - let mut widget_override = std::mem::replace(&mut WidgetOverride(Box::new())); - layout.extend(widget_override.0(node_id, context)); - let empty_widget_override = context - .network_interface - .widget_override(&node_id, input_index, context.selection_network_path) - .replace(&mut widget_override); - } else { + let row = context.call_widget_override(&node_id, input_index).unwrap_or_else(|| { let input_type = context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path); - let row = property_from_type(node_id, input_index, &input_type.0, context); - layout.extend(row); - } + property_from_type(node_id, input_index, &input_type.0, context) + }); + layout.extend(row); } if layout.is_empty() { layout = node_no_properties(node_id, context); 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 fcfad70f3d..d8861f8418 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3,11 +3,9 @@ use super::misc::PTZ; use super::nodes::SelectedNodes; use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP}; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; -use crate::messages::portfolio::document::node_graph::document_node_definitions::{self, resolve_document_node_type, DocumentNodeDefinition, NodePropertiesContext}; -use crate::messages::portfolio::document::node_graph::node_properties; +use crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_document_node_type, DocumentNodeDefinition}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::tool::common_functionality::graph_modification_utils; -use crate::messages::tool::tool_messages::tool_prelude::LayoutGroup; use bezier_rs::Subpath; use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; @@ -16,6 +14,7 @@ use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::transform::Footprint; use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY}; +use serde_json::Value; use glam::{DAffine2, DVec2, IVec2}; use std::collections::{HashMap, HashSet, VecDeque}; @@ -732,10 +731,14 @@ impl NodeNetworkInterface { let (input_type, type_source) = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); let data_type = FrontendGraphDataType::with_type(&input_type); - let import_name = if properties_row.input_name.is_empty() { + let Some(input_name) = properties_row.input_data.get("input_name").and_then(|input_name| input_name.as_str()) else { + log::error!("Could not get input_name in frontend_imports"); + return None; + }; + let import_name = if input_name.is_empty() { input_type.clone().nested_type().to_string() } else { - properties_row.input_name + input_name.to_string() }; let connected_to = self @@ -1042,19 +1045,25 @@ impl NodeNetworkInterface { .and_then(|node_metadata| node_metadata.persistent_metadata.reference.as_ref().map(|reference| reference.to_string())) } + pub fn input_name(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&str> { + let Some(value) = self.input_metadata(node_id, index, "input_name", network_path) else { + log::error!("Could not get input_name for node {node_id} index {index}"); + return None; + }; + value.as_str() + } + pub fn input_properties_row(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&PropertiesRow> { self.node_metadata(node_id, network_path) .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index)) } - pub fn input_properties_row_mut(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&mut PropertiesRow> { - self.node_metadata_mut(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get_mut(index)) - } - - pub fn widget_override(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&mut WidgetOverride> { - self.input_properties_row_mut(node_id, index, network_path) - .and_then(|node_metadata| node_metadata.widget_override.as_mut()) + pub fn input_metadata(&self, node_id: &NodeId, index: usize, field: &str, network_path: &[NodeId]) -> Option<&Value> { + let Some(input_row) = self.input_properties_row(node_id, index, network_path) else { + log::error!("Could not get node_metadata in get_input_metadata"); + return None; + }; + input_row.input_data.get(field) } // Use frontend display name instead @@ -3211,7 +3220,7 @@ impl NodeNetworkInterface { } /// Inserts a new export at insert index. If the insert index is -1 it is inserted at the end. The output_name is used by the encapsulating node. - pub fn add_export(&mut self, default_value: TaggedValue, insert_index: isize, output_name: String, network_path: &[NodeId]) { + pub fn add_export(&mut self, default_value: TaggedValue, insert_index: isize, output_name: &str, network_path: &[NodeId]) { let Some(network) = self.network_mut(network_path) else { log::error!("Could not get nested network in add_export"); return; @@ -3237,9 +3246,9 @@ impl NodeNetworkInterface { // There will not be an encapsulating node if the network is the document network if let Some(encapsulating_node_metadata) = self.encapsulating_node_metadata_mut(network_path) { if insert_index == -1 { - encapsulating_node_metadata.persistent_metadata.output_names.push(output_name); + encapsulating_node_metadata.persistent_metadata.output_names.push(output_name.to_string()); } else { - encapsulating_node_metadata.persistent_metadata.output_names.insert(insert_index as usize, output_name); + encapsulating_node_metadata.persistent_metadata.output_names.insert(insert_index as usize, output_name.to_string()); } }; @@ -3266,7 +3275,7 @@ impl NodeNetworkInterface { } /// Inserts a new input at insert index. If the insert index is -1 it is inserted at the end. The output_name is used by the encapsulating node. - pub fn add_import(&mut self, default_value: TaggedValue, exposed: bool, insert_index: isize, input_name: String, network_path: &[NodeId]) { + pub fn add_import(&mut self, default_value: TaggedValue, exposed: bool, insert_index: isize, input_name: &str, network_path: &[NodeId]) { let mut encapsulating_network_path = network_path.to_vec(); let Some(node_id) = encapsulating_network_path.pop() else { log::error!("Cannot add import for document network"); @@ -5731,80 +5740,65 @@ impl PartialEq for DocumentNodeMetadata { } } -pub trait CloneableFn: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Send + Sync { - fn clone_box(&self) -> Box; -} - -impl CloneableFn for T -where - T: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Clone + Send + Sync + 'static, -{ - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} - -use std::fmt; - -// TODO: Store the lambdas as enums which can then be mapped into lambdas when serializing/deserializing/cloning -pub trait CloneableFn: Fn(NodeId, &mut NodePropertiesContext) -> Vec + Send + Sync { - fn clone_box(&self) -> Box; -} - -impl fmt::Debug for dyn CloneableFn { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "CloneableFn") - } -} - -#[derive(Debug, serde::Serialize, serde::Deserialize)] -pub struct WidgetOverride(pub Box); - -impl Clone for WidgetOverride { - fn clone(&self) -> Self { - WidgetOverride(self.0.clone_box()) - } -} - -impl WidgetOverride { - pub fn hidden() -> Self { - WidgetOverride(Box::new(|_node_id: NodeId, _context| Vec::new())) - } - - pub fn string(string: &'static str) -> Self { - WidgetOverride(Box::new(|_node_id: NodeId, _context| node_properties::string_properties(string))) - } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub enum WidgetOverride { + None, + Hidden, + String(String), + Custom(String), } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. /// If the string is empty/DNE, then it uses the type. - pub input_name: String, + // pub input_name: String, + /// A general datastore than can store key value pairs of any types for any input + pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type - pub widget_override: Option, + // The string is the identifier to the widget override function stored in INPUT_OVERRIDES + pub widget_override: Option, } impl PartialEq for PropertiesRow { fn eq(&self, other: &Self) -> bool { - self.input_name == other.input_name + self.input_data == other.input_data && self.widget_override == other.widget_override } } impl From<&str> for PropertiesRow { fn from(input_name: &str) -> Self { - PropertiesRow { - input_name: input_name.to_string(), - widget_override: None, - } + PropertiesRow::with_override(input_name, WidgetOverride::None) } } +// impl From for PropertiesRow { +// fn from(input_name: String) -> Self { +// PropertiesRow::with_override(&input_name, with_override) +// } +// } + impl PropertiesRow { pub fn with_override(input_name: &str, widget_override: WidgetOverride) -> Self { - PropertiesRow { - input_name: input_name.to_string(), - widget_override: Some(widget_override), + let mut input_data = HashMap::new(); + input_data.insert("input_name".to_string(), Value::String(input_name.to_string())); + match widget_override { + WidgetOverride::None => PropertiesRow { input_data, widget_override: None }, + WidgetOverride::Hidden => PropertiesRow { + input_data, + widget_override: Some("hidden".to_string()), + }, + WidgetOverride::String(string_properties) => { + input_data.insert("string_properties".to_string(), Value::String(string_properties)); + PropertiesRow { + input_data, + widget_override: Some("string".to_string()), + } + } + WidgetOverride::Custom(lambda_name) => PropertiesRow { + input_data, + widget_override: Some(lambda_name), + }, } } } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index bb7f976aab..e1acdc891c 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -770,7 +770,7 @@ impl EditorHandle { document .network_interface .replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode")); - document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "".to_string(), &[node_id]); + document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "", &[node_id]); } } } From f1fbccb7a73ba7bade7eacc762608bb954b8fd4c Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 23 Nov 2024 23:56:20 -0800 Subject: [PATCH 05/19] Migrate noise pattern node to input properties --- .../node_graph/document_node_definitions.rs | 224 +++++++++++++++-- .../document/node_graph/node_properties.rs | 235 ++++++------------ .../utility_types/network_interface.rs | 12 +- 3 files changed, 282 insertions(+), 189 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 06edab8921..8c4ede29d3 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 @@ -792,18 +792,18 @@ fn static_nodes() -> Vec { "Clip".into(), "Seed".into(), PropertiesRow::with_override("Scale", WidgetOverride::Custom("noise_properties_scale".to_string())), - "Noise Type".into(), - "Domain Warp Type".into(), - "Domain Warp Amplitude".into(), - "Fractal Type".into(), - "Fractal Octaves".into(), - "Fractal Lacunarity".into(), - "Fractal Gain".into(), - "Fractal Weighted Strength".into(), - "Fractal Ping Pong Strength".into(), - "Cellular Distance Function".into(), - "Cellular Return Type".into(), - "Cellular Jitter".into(), + PropertiesRow::with_override("Noise Type", WidgetOverride::Custom("noise_properties_noise_type".to_string())), + PropertiesRow::with_override("Domain Warp Type", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())), + PropertiesRow::with_override("Domain Warp Amplitude", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())), + PropertiesRow::with_override("Fractal Type", WidgetOverride::Custom("noise_properties_fractal_type".to_string())), + PropertiesRow::with_override("Fractal Octaves", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())), + PropertiesRow::with_override("Fractal Lacunarity", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())), + PropertiesRow::with_override("Fractal Gain", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())), + PropertiesRow::with_override("Fractal Weighted Strength", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())), + PropertiesRow::with_override("Fractal Ping Pong Strength", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())), + PropertiesRow::with_override("Cellular Distance Function", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())), + PropertiesRow::with_override("Cellular Return Type", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())), + PropertiesRow::with_override("Cellular Jitter", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())), ], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { @@ -2736,22 +2736,196 @@ fn static_input_properties() -> HashMap Some(noise_type), - _ => None, - }); - let coherent_noise_active = current_noise_type != Some(NoiseType::WhiteNoise); - let input_name = context - .network_interface - .input_name(&node_id, index, context.selection_network_path) - .ok_or("input name not found in noise_properties_scale")?; - - let scale = node_properties::number_widget(document_node, node_id, 2, input_name, NumberInput::default().min(0.).disabled(!coherent_noise_active), true); + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let scale = node_properties::number_widget(document_node, node_id, index, input_name, NumberInput::default().min(0.).disabled(!coherent_noise_active), true); Ok(vec![scale.into()]) }), ); + map.insert( + "noise_properties_noise_type".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let noise_type_row = node_properties::noise_type(document_node, node_id, index, input_name, true); + Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }]) + }), + ); + map.insert( + "noise_properties_domain_warp_type".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let domain_warp_type = node_properties::domain_warp_type(document_node, node_id, index, input_name, true, !coherent_noise_active); + Ok(vec![domain_warp_type.into()]) + }), + ); + map.insert( + "noise_properties_domain_warp_amplitude".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let domain_warp_amplitude = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), + true, + ); + Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }]) + }), + ); + map.insert( + "noise_properties_fractal_type".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_type_row = node_properties::fractal_type(document_node, node_id, index, input_name, true, !coherent_noise_active); + Ok(vec![fractal_type_row.into()]) + }), + ); + map.insert( + "noise_properties_fractal_octaves".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_octaves = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .min(1.) + .max(10.) + .range_max(Some(4.)) + .is_integer(true) + .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), + true, + ); + Ok(vec![fractal_octaves.into()]) + }), + ); + map.insert( + "noise_properties_fractal_lacunarity".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_lacunarity = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .min(0.) + .range_max(Some(10.)) + .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), + true, + ); + Ok(vec![fractal_lacunarity.into()]) + }), + ); + map.insert( + "noise_properties_fractal_gain".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_gain = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .min(0.) + .range_max(Some(10.)) + .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), + true, + ); + Ok(vec![fractal_gain.into()]) + }), + ); + map.insert( + "noise_properties_fractal_weighted_strength".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_weighted_strength = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .min(0.) + .max(1.) // Defined for the 0-1 range + .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), + true, + ); + Ok(vec![fractal_weighted_strength.into()]) + }), + ); + map.insert( + "noise_properties_ping_pong_strength".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; + let fractal_ping_pong_strength = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .min(0.) + .range_max(Some(10.)) + .disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), + true, + ); + Ok(vec![fractal_ping_pong_strength.into(), LayoutGroup::Row { widgets: Vec::new() }]) + }), + ); + map.insert( + "noise_properties_cellular_distance_function".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let cellular_distance_function_row = node_properties::cellular_distance_function(document_node, node_id, index, input_name, true, !coherent_noise_active || !cellular_noise_active); + Ok(vec![cellular_distance_function_row.into()]) + }), + ); + map.insert( + "noise_properties_cellular_return_type".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let cellular_return_type = node_properties::cellular_return_type(document_node, node_id, index, input_name, true, !coherent_noise_active || !cellular_noise_active); + Ok(vec![cellular_return_type.into()]) + }), + ); + map.insert( + "noise_properties_cellular_jitter".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; + let cellular_jitter = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default() + .mode_range() + .range_min(Some(0.)) + .range_max(Some(1.)) + .disabled(!coherent_noise_active || !cellular_noise_active), + true, + ); + Ok(vec![cellular_jitter.into()]) + }), + ); + map } 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 26489b5c54..c42c5f4e2a 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -49,7 +49,7 @@ fn commit_value(_: &T) -> Message { DocumentMessage::AddTransaction.into() } -fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder { +pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType, exposed: bool) -> WidgetHolder { ParameterExposeButton::new() .exposed(exposed) .data_type(data_type) @@ -66,7 +66,7 @@ fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphDataType } // TODO: Remove this when we have proper entry row formatting that includes room for Assists. -fn add_blank_assist(widgets: &mut Vec) { +pub fn add_blank_assist(widgets: &mut Vec) { widgets.extend_from_slice(&[ // Custom CSS specific to the Properties panel converts this Section separator into the width of an assist (24px). Separator::new(SeparatorType::Section).widget_holder(), @@ -75,7 +75,7 @@ fn add_blank_assist(widgets: &mut Vec) { ]); } -fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec { +pub fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, data_type: FrontendGraphDataType, blank_assist: bool) -> Vec { let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -247,7 +247,7 @@ pub(crate) fn property_from_type(node_id: NodeId, index: usize, ty: &Type, conte extra_widgets } -fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { +pub fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -266,7 +266,7 @@ fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name widgets } -fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { +pub fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -285,7 +285,7 @@ fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, widgets } -fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, checkbox_input: CheckboxInput, blank_assist: bool) -> Vec { +pub fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, checkbox_input: CheckboxInput, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -305,7 +305,7 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name widgets } -fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> Vec { +pub fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> Vec { let mut location_widgets = start_widgets(document_node, node_id, index, "Footprint", FrontendGraphDataType::General, true); location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -446,7 +446,17 @@ fn footprint_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) ] } -fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, unit: &str, min: Option, mut assist: impl FnMut(&mut Vec)) -> LayoutGroup { +pub fn vec2_widget( + document_node: &DocumentNode, + node_id: NodeId, + index: usize, + name: &str, + x: &str, + y: &str, + unit: &str, + min: Option, + mut assist: impl FnMut(&mut Vec), +) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, false); assist(&mut widgets); @@ -536,7 +546,7 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name LayoutGroup::Row { widgets } } -fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_input: TextInput, blank_assist: bool) -> Vec { +pub fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_input: TextInput, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist); let from_string = |string: &str| { @@ -565,7 +575,7 @@ fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na widgets } -fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec { +pub fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist); let from_string = |string: &str| { @@ -594,7 +604,7 @@ fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, widgets } -fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> (Vec, Option>) { +pub fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> (Vec, Option>) { let mut first_widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let mut second_widgets = None; @@ -628,7 +638,7 @@ fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name (first_widgets, second_widgets) } -fn vector_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { +pub fn vector_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::VectorData, blank_assist); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); @@ -676,7 +686,7 @@ pub fn number_widget(document_node: &DocumentNode, node_id: NodeId, index: usize } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -703,7 +713,7 @@ fn color_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, na LayoutGroup::Row { widgets }.with_tooltip("Color Channel") } -fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -731,7 +741,7 @@ fn rgba_channel(document_node: &DocumentNode, node_id: NodeId, index: usize, nam } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn noise_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn noise_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -757,7 +767,7 @@ fn noise_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn fractal_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { +pub fn fractal_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -783,7 +793,7 @@ fn fractal_type(document_node: &DocumentNode, node_id: NodeId, index: usize, nam } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn cellular_distance_function(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { +pub fn cellular_distance_function(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -812,7 +822,7 @@ fn cellular_distance_function(document_node: &DocumentNode, node_id: NodeId, ind } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn cellular_return_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { +pub fn cellular_return_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -838,7 +848,7 @@ fn cellular_return_type(document_node: &DocumentNode, node_id: NodeId, index: us } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn domain_warp_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { +pub fn domain_warp_type(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool, disabled: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -864,7 +874,7 @@ fn domain_warp_type(document_node: &DocumentNode, node_id: NodeId, index: usize, } // TODO: Generalize this instead of using a separate function per dropdown menu enum -fn blend_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn blend_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -897,7 +907,7 @@ fn blend_mode(document_node: &DocumentNode, node_id: NodeId, index: usize, name: } // TODO: Generalize this for all dropdowns (also see blend_mode and channel_extration) -fn luminance_calculation(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn luminance_calculation(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -924,7 +934,7 @@ fn luminance_calculation(document_node: &DocumentNode, node_id: NodeId, index: u LayoutGroup::Row { widgets }.with_tooltip("Formula used to calculate the luminance of a pixel") } -fn boolean_operation_radio_buttons(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn boolean_operation_radio_buttons(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -954,7 +964,7 @@ fn boolean_operation_radio_buttons(document_node: &DocumentNode, node_id: NodeId LayoutGroup::Row { widgets } } -fn line_cap_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn line_cap_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -979,7 +989,7 @@ fn line_cap_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, LayoutGroup::Row { widgets } } -fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -1004,7 +1014,7 @@ fn line_join_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, LayoutGroup::Row { widgets } } -fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_button: ColorButton, blank_assist: bool) -> LayoutGroup { +pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_button: ColorButton, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); // Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel @@ -1049,7 +1059,7 @@ fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, nam LayoutGroup::Row { widgets } } -fn curves_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { +pub fn curves_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist); let Some(input) = document_node.inputs.get(index) else { @@ -1068,7 +1078,7 @@ fn curves_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, na LayoutGroup::Row { widgets } } -fn centroid_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> LayoutGroup { +pub fn centroid_widget(document_node: &DocumentNode, node_id: NodeId, index: usize) -> LayoutGroup { let mut widgets = start_widgets(document_node, node_id, index, "Centroid Type", FrontendGraphDataType::General, true); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); @@ -1119,22 +1129,37 @@ pub(crate) fn insert_channel_properties(document_node: &DocumentNode, node_id: N vec![color_channel] } -// Noise Type is commented out for now as there is only one type of noise (White Noise). -// As soon as there are more types of noise, this should be uncommented. -pub(crate) fn noise_pattern_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - // Get the current values of the inputs of interest so they can set whether certain inputs are disabled based on various conditions. - let current_noise_type = match &document_node.inputs[3].as_value() { +pub fn query_node_and_input_name<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str), String> { + let network = context + .network_interface + .network(context.selection_network_path) + .ok_or("network not found in query_node_and_input_name")?; + let document_node = network.nodes.get(&node_id).ok_or("node not found in query_node_and_input_name")?; + let input_name = context + .network_interface + .input_name(&node_id, input_index, context.selection_network_path) + .ok_or("input name not found in noise_properties_scale")?; + Ok((document_node, input_name)) +} + +pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> { + let network = context + .network_interface + .network(context.selection_network_path) + .ok_or("network not found in query_noise_pattern_state")?; + let document_node = network.nodes.get(&node_id).ok_or("node not found in query_noise_pattern_state")?; + let current_noise_type = document_node.inputs.iter().find_map(|input| match input.as_value() { Some(&TaggedValue::NoiseType(noise_type)) => Some(noise_type), _ => None, - }; - let current_domain_warp_type = match &document_node.inputs[4].as_value() { - Some(&TaggedValue::DomainWarpType(domain_warp_type)) => Some(domain_warp_type), - _ => None, - }; - let current_fractal_type = match &document_node.inputs[6].as_value() { + }); + let current_fractal_type = document_node.inputs.iter().find_map(|input| match input.as_value() { Some(&TaggedValue::FractalType(fractal_type)) => Some(fractal_type), _ => None, - }; + }); + let current_domain_warp_type = document_node.inputs.iter().find_map(|input| match input.as_value() { + Some(&TaggedValue::DomainWarpType(domain_warp_type)) => Some(domain_warp_type), + _ => None, + }); let fractal_active = current_fractal_type != Some(FractalType::None); let coherent_noise_active = current_noise_type != Some(NoiseType::WhiteNoise); let cellular_noise_active = current_noise_type == Some(NoiseType::Cellular); @@ -1143,130 +1168,14 @@ pub(crate) fn noise_pattern_properties(document_node: &DocumentNode, node_id: No let domain_warp_only_fractal_type_wrongly_active = !domain_warp_active && (current_fractal_type == Some(FractalType::DomainWarpIndependent) || current_fractal_type == Some(FractalType::DomainWarpProgressive)); - // All - let clip = LayoutGroup::Row { - widgets: bool_widget(document_node, node_id, 0, "Clip", CheckboxInput::default(), true), - }; - let seed = number_widget(document_node, node_id, 1, "Seed", NumberInput::default().min(0.).is_integer(true), true); - let scale = number_widget(document_node, node_id, 2, "Scale", NumberInput::default().min(0.).disabled(!coherent_noise_active), true); - let noise_type_row = noise_type(document_node, node_id, 3, "Noise Type", true); - - // Domain Warp - let domain_warp_type_row = domain_warp_type(document_node, node_id, 4, "Domain Warp Type", true, !coherent_noise_active); - let domain_warp_amplitude = number_widget( - document_node, - node_id, - 5, - "Domain Warp Amplitude", - NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), - true, - ); - - // Fractal - let fractal_type_row = fractal_type(document_node, node_id, 6, "Fractal Type", true, !coherent_noise_active); - let fractal_octaves = number_widget( - document_node, - node_id, - 7, - "Fractal Octaves", - NumberInput::default() - .mode_range() - .min(1.) - .max(10.) - .range_max(Some(4.)) - .is_integer(true) - .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), - true, - ); - let fractal_lacunarity = number_widget( - document_node, - node_id, - 8, - "Fractal Lacunarity", - NumberInput::default() - .mode_range() - .min(0.) - .range_max(Some(10.)) - .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), - true, - ); - let fractal_gain = number_widget( - document_node, - node_id, - 9, - "Fractal Gain", - NumberInput::default() - .mode_range() - .min(0.) - .range_max(Some(10.)) - .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), - true, - ); - let fractal_weighted_strength = number_widget( - document_node, - node_id, - 10, - "Fractal Weighted Strength", - NumberInput::default() - .mode_range() - .min(0.) - .max(1.) // Defined for the 0-1 range - .disabled(!coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), - true, - ); - let fractal_ping_pong_strength = number_widget( - document_node, - node_id, - 11, - "Fractal Ping Pong Strength", - NumberInput::default() - .mode_range() - .min(0.) - .range_max(Some(10.)) - .disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active), - true, - ); - - // Cellular - let cellular_distance_function_row = cellular_distance_function(document_node, node_id, 12, "Cellular Distance Function", true, !coherent_noise_active || !cellular_noise_active); - let cellular_return_type = cellular_return_type(document_node, node_id, 13, "Cellular Return Type", true, !coherent_noise_active || !cellular_noise_active); - let cellular_jitter = number_widget( - document_node, - node_id, - 14, - "Cellular Jitter", - NumberInput::default() - .mode_range() - .range_min(Some(0.)) - .range_max(Some(1.)) - .disabled(!coherent_noise_active || !cellular_noise_active), - true, - ); - - vec![ - // All - clip, - LayoutGroup::Row { widgets: seed }, - LayoutGroup::Row { widgets: scale }, - noise_type_row, - LayoutGroup::Row { widgets: Vec::new() }, - // Domain Warp - domain_warp_type_row, - LayoutGroup::Row { widgets: domain_warp_amplitude }, - LayoutGroup::Row { widgets: Vec::new() }, - // Fractal - fractal_type_row, - LayoutGroup::Row { widgets: fractal_octaves }, - LayoutGroup::Row { widgets: fractal_lacunarity }, - LayoutGroup::Row { widgets: fractal_gain }, - LayoutGroup::Row { widgets: fractal_weighted_strength }, - LayoutGroup::Row { widgets: fractal_ping_pong_strength }, - LayoutGroup::Row { widgets: Vec::new() }, - // Cellular - cellular_distance_function_row, - cellular_return_type, - LayoutGroup::Row { widgets: cellular_jitter }, - ] + Ok(( + fractal_active, + coherent_noise_active, + cellular_noise_active, + ping_pong_active, + domain_warp_active, + domain_warp_only_fractal_type_wrongly_active, + )) } pub(crate) fn brightness_contrast_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { 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 d8861f8418..c52513e37f 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -2763,13 +2763,23 @@ impl NodeNetworkInterface { } pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool { - let input_count = self.number_of_displayed_inputs(node_id, network_path); + let Some(network) = self.network(network_path) else { + log::error!("Could not get network in is_eligible_to_be_layer"); + return false; + }; + let Some(node) = network.nodes.get(node_id) else { + log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); + return false; + }; + let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed_to_frontend(network_path.is_empty())).count(); + let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed_to_frontend(network_path.is_empty())); let output_count = self.number_of_outputs(node_id, network_path); self.node_metadata(node_id, network_path) .is_some_and(|node_metadata| node_metadata.persistent_metadata.has_primary_output) && output_count == 1 && (input_count <= 2) + && parameters_hidden } pub fn node_graph_ptz(&self, network_path: &[NodeId]) -> Option<&PTZ> { From 2e40f5f97ba8dbf83b612024b81028851ae175e0 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 28 Nov 2024 01:27:11 -0800 Subject: [PATCH 06/19] Reorder exports --- .../src/messages/frontend/frontend_message.rs | 8 + .../document/document_message_handler.rs | 2 +- .../document/node_graph/node_graph_message.rs | 2 + .../node_graph/node_graph_message_handler.rs | 189 +++++++++++------- .../document/node_graph/node_properties.rs | 3 +- .../utility_types/network_interface.rs | 108 ++++++++-- frontend/src/components/views/Graph.svelte | 41 +++- frontend/src/state-providers/node-graph.ts | 16 ++ frontend/src/wasm-communication/messages.ts | 10 + 9 files changed, 293 insertions(+), 86 deletions(-) diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index f5482cdeb1..6629eb02aa 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -155,6 +155,14 @@ pub enum FrontendMessage { UpdateGraphViewOverlay { open: bool, }, + UpdateImportReorderIndex { + #[serde(rename = "importIndex")] + index: Option, + }, + UpdateExportReorderIndex { + #[serde(rename = "exportIndex")] + index: Option, + }, UpdateLayerWidths { #[serde(rename = "layerWidths")] layer_widths: HashMap, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 74482810f6..693fe43f41 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1008,7 +1008,7 @@ impl MessageHandler> for DocumentMessag } responses.add(PropertiesPanelMessage::Refresh); responses.add(NodeGraphMessage::UpdateLayerPanel); - responses.add(NodeGraphMessage::UpdateInSelectedNetwork) + responses.add(NodeGraphMessage::UpdateInSelectedNetwork); } DocumentMessage::SetBlendModeForSelectedLayers { blend_mode } => { for layer in self.network_interface.selected_nodes(&[]).unwrap().selected_layers_except_artboards(&self.network_interface) { 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 b5b10b63a1..7f6bde8335 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 @@ -100,6 +100,8 @@ pub enum NodeGraphMessage { PrintSelectedNodeCoordinates, RemoveImport { import_index: usize }, RemoveExport { export_index: usize }, + ReorderImport { start_index: usize, end_index: usize }, + ReorderExport { start_index: usize, end_index: usize }, RunDocumentGraph, ForceRunDocumentGraph, SelectedNodesAdd { 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 204eb37bef..44d6c08728 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 @@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{ self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, }; -use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; +use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; @@ -71,6 +71,12 @@ pub struct NodeGraphMessageHandler { auto_panning: AutoPanning, /// The node to preview on mouse up if alt-clicked preview_on_mouse_up: Option, + // The index of the import that is being moved + reordering_import: Option, + // The index of the export that is being moved + reordering_export: Option, + // The end index of the moved port + end_index: Option, } /// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network. @@ -565,29 +571,6 @@ impl<'a> MessageHandler> for NodeGrap let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click); - let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { - log::error!("Could not get modify import export in PointerDown"); - return; - }; - - if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() { - responses.add(DocumentMessage::AddTransaction); - responses.add(NodeGraphMessage::AddExport); - return; - } else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() { - responses.add(DocumentMessage::AddTransaction); - responses.add(NodeGraphMessage::AddImport); - return; - } else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) { - responses.add(DocumentMessage::AddTransaction); - responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index }); - return; - } else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) { - responses.add(DocumentMessage::AddTransaction); - responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index }); - return; - } - if network_interface .layer_click_target_from_click(click, network_interface::LayerClickTargetTypes::Grip, selection_network_path) .is_some() @@ -663,6 +646,37 @@ impl<'a> MessageHandler> for NodeGrap return; } + let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { + log::error!("Could not get modify import export in PointerDown"); + return; + }; + + if modify_import_export.add_import_export.clicked_input_port_from_point(node_graph_point).is_some() { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::AddExport); + return; + } else if modify_import_export.add_import_export.clicked_output_port_from_point(node_graph_point).is_some() { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::AddImport); + return; + } else if let Some(remove_import_index) = modify_import_export.remove_imports_exports.clicked_output_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveImport { import_index: remove_import_index }); + return; + } else if let Some(remove_export_index) = modify_import_export.remove_imports_exports.clicked_input_port_from_point(node_graph_point) { + responses.add(DocumentMessage::AddTransaction); + responses.add(NodeGraphMessage::RemoveExport { export_index: remove_export_index }); + return; + } else if let Some(move_import_index) = modify_import_export.reorder_imports_exports.clicked_output_port_from_point(node_graph_point) { + responses.add(DocumentMessage::StartTransaction); + self.reordering_import = Some(move_import_index); + return; + } else if let Some(move_export_index) = modify_import_export.reorder_imports_exports.clicked_input_port_from_point(node_graph_point) { + responses.add(DocumentMessage::StartTransaction); + self.reordering_export = Some(move_export_index); + return; + } + self.selection_before_pointer_down = network_interface .selected_nodes(selection_network_path) .map(|selected_nodes| selected_nodes.selected_nodes().cloned().collect()) @@ -893,6 +907,46 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: true }); } else if self.box_selection_start.is_some() { responses.add(NodeGraphMessage::UpdateBoxSelection); + } else if self.reordering_import.is_some() { + let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { + log::error!("Could not get modify import export in PointerUp"); + return; + }; + // Find the first import that is below the mouse position + self.end_index = Some( + modify_import_export + .reorder_imports_exports + .output_ports() + .find_map(|(index, click_target)| { + let Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else { + log::error!("Could not get bounding box for import: {index}"); + return None; + }; + (position > point.y).then_some(*index) + }) + .unwrap_or(modify_import_export.reorder_imports_exports.output_ports().count()), + ); + responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index }); + } else if self.reordering_export.is_some() { + let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { + log::error!("Could not get modify import export in PointerUp"); + return; + }; + // Find the first export that is below the mouse position + self.end_index = Some( + modify_import_export + .reorder_imports_exports + .input_ports() + .find_map(|(index, click_target)| { + let Some(position) = click_target.bounding_box().map(|bbox| (bbox[0].y + bbox[1].y) / 2.) else { + log::error!("Could not get bounding box for export: {index}"); + return None; + }; + (position > point.y).then_some(*index) + }) + .unwrap_or(modify_import_export.reorder_imports_exports.input_ports().count()), + ); + responses.add(FrontendMessage::UpdateExportReorderIndex { index: self.end_index }); } } NodeGraphMessage::PointerUp => { @@ -1141,15 +1195,34 @@ impl<'a> MessageHandler> for NodeGrap } self.select_if_not_dragged = None; } - + // End of reordering an import + else if let (Some(moving_import), Some(end_index)) = (self.reordering_import, self.end_index) { + responses.add(NodeGraphMessage::ReorderImport { + start_index: moving_import, + end_index, + }); + responses.add(DocumentMessage::EndTransaction); + } + // End of reordering an export + else if let (Some(moving_export), Some(end_index)) = (self.reordering_export, self.end_index) { + responses.add(NodeGraphMessage::ReorderExport { + start_index: moving_export, + end_index, + }); + responses.add(DocumentMessage::EndTransaction); + } self.drag_start = None; self.begin_dragging = false; self.box_selection_start = None; self.wire_in_progress_from_connector = None; self.wire_in_progress_to_connector = None; + self.reordering_export = None; + self.reordering_import = None; responses.add(DocumentMessage::EndTransaction); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); - responses.add(FrontendMessage::UpdateBox { box_selection: None }) + responses.add(FrontendMessage::UpdateBox { box_selection: None }); + responses.add(FrontendMessage::UpdateImportReorderIndex { index: None }); + responses.add(FrontendMessage::UpdateExportReorderIndex { index: None }); } NodeGraphMessage::PointerOutsideViewport { shift } => { if self.drag_start.is_some() || self.box_selection_start.is_some() { @@ -1208,6 +1281,16 @@ impl<'a> MessageHandler> for NodeGrap responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::RunDocumentGraph); } + NodeGraphMessage::ReorderImport { start_index, end_index } => { + network_interface.reorder_import(start_index, end_index, selection_network_path); + responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::RunDocumentGraph); + } + NodeGraphMessage::ReorderExport { start_index, end_index } => { + network_interface.reorder_export(start_index, end_index, selection_network_path); + responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::RunDocumentGraph); + } NodeGraphMessage::RunDocumentGraph => { responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false }); } @@ -1431,13 +1514,7 @@ impl<'a> MessageHandler> for NodeGrap let node_ids = selected_nodes.selected_nodes().cloned().collect::>(); // If any of the selected nodes are pinned, unpin them all. Otherwise, pin them all. - let pinned = !node_ids.iter().all(|node_id| { - if let Some(node) = network_interface.node_metadata(node_id, breadcrumb_network_path) { - node.persistent_metadata.pinned - } else { - false - } - }); + let pinned = !node_ids.iter().all(|node_id| network_interface.is_pinned(node_id, breadcrumb_network_path)); responses.add(DocumentMessage::AddTransaction); for node_id in &node_ids { @@ -1901,23 +1978,13 @@ impl NodeGraphMessageHandler { match layers.len() { // If no layers are selected, show properties for all selected nodes 0 => { - let selected_nodes = nodes - .iter() - .map(|node_id| { - let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { - node.persistent_metadata.pinned - } else { - error!("Could not get node {node_id} in collate_properties"); - false - }; - - node_properties::generate_node_properties(*node_id, pinned, context) - }) - .collect::>(); + let selected_nodes = nodes.iter().map(|node_id| node_properties::generate_node_properties(*node_id, context)).collect::>(); if !selected_nodes.is_empty() { return selected_nodes; } + // TODO: Display properties for encapsulating node when no nodes are selected in a nested network + // This may require store a separate path for the properties panel let mut properties = vec![LayoutGroup::Row { widgets: vec![ Separator::new(SeparatorType::Related).widget_holder(), @@ -1943,18 +2010,10 @@ impl NodeGraphMessageHandler { .collect::>() .iter() .filter_map(|node_id| { - let pinned = if let Some(node) = context.network_interface.node_metadata(node_id, context.selection_network_path) { - node.persistent_metadata.pinned - } else { - error!("Could not get node {node_id} in collate_properties"); - false - }; - - if pinned { - Some(node_properties::generate_node_properties(*node_id, pinned, context)) - } else { - None - } + context + .network_interface + .is_pinned(node_id, context.selection_network_path) + .then(|| node_properties::generate_node_properties(*node_id, context)) }) .collect::>(); @@ -2023,16 +2082,7 @@ impl NodeGraphMessageHandler { }) .collect::>() .iter() - .map(|(_, node_id)| { - let pinned = if let Some(node) = context.network_interface.node_metadata(&node_id, context.selection_network_path) { - node.persistent_metadata.pinned - } else { - error!("Could not get node {node_id} in collate_properties"); - false - }; - - node_properties::generate_node_properties(*node_id, pinned, context) - }) + .map(|(_, node_id)| node_properties::generate_node_properties(*node_id, context)) .collect::>(); layer_properties.extend(node_properties); @@ -2500,6 +2550,9 @@ impl Default for NodeGraphMessageHandler { deselect_on_pointer_up: None, auto_panning: Default::default(), preview_on_mouse_up: None, + reordering_export: None, + reordering_import: None, + end_index: None, } } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index c42c5f4e2a..1bd820e62e 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -2166,7 +2166,7 @@ pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _c vec![LayoutGroup::Row { widgets: index }] } -pub(crate) fn generate_node_properties(node_id: NodeId, pinned: bool, context: &mut NodePropertiesContext) -> LayoutGroup { +pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup { let mut layout = Vec::new(); let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); @@ -2182,6 +2182,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, pinned: bool, context: & } let name = context.network_interface.reference(&node_id, context.selection_network_path).clone().unwrap_or_default(); let visible = context.network_interface.is_visible(&node_id, context.selection_network_path); + let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path); LayoutGroup::Section { name, visible, 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 c52513e37f..ae04163eea 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1102,6 +1102,14 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.locked } + pub fn is_pinned(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { + let Some(node_metadata) = self.node_metadata(node_id, network_path) else { + log::error!("Could not get persistent node metadata in is_pinned for node {node_id}"); + return false; + }; + node_metadata.persistent_metadata.pinned + } + pub fn is_visible(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { let Some(network) = self.network(network_path) else { log::error!("Could not get nested network_metadata in is_visible"); @@ -1970,6 +1978,7 @@ impl NodeNetworkInterface { return; }; + let mut reorder_imports_exports = Ports::new(); let mut add_import_export = Ports::new(); let mut remove_imports_exports = Ports::new(); @@ -2030,20 +2039,34 @@ impl NodeNetworkInterface { return; }; - for (export_index, export_click_target) in import_exports.input_ports() { - let Some(export_bounding_box) = export_click_target.bounding_box() else { + for (import_index, import_click_target) in import_exports.output_ports() { + let Some(import_bounding_box) = import_click_target.bounding_box() else { log::error!("Could not get export bounding box in load_modify_import_export"); continue; }; - remove_imports_exports.insert_input_port_at_center(*export_index, (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(16., 0.)); + let reorder_import_center = (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-12., 0.); + let remove_import_center = reorder_import_center + DVec2::new(-12., 0.); + + let reorder_import = ClickTarget::new(Subpath::new_rect(reorder_import_center - DVec2::new(3., 4.), reorder_import_center + DVec2::new(3., 4.)), 0.); + let remove_import = ClickTarget::new(Subpath::new_rect(remove_import_center - DVec2::new(8., 8.), remove_import_center + DVec2::new(8., 8.)), 0.); + + reorder_imports_exports.insert_custom_output_port(*import_index, reorder_import); + remove_imports_exports.insert_custom_output_port(*import_index, remove_import); } - for (import_index, import_click_target) in import_exports.output_ports() { - let Some(import_bounding_box) = import_click_target.bounding_box() else { + for (export_index, export_click_target) in import_exports.input_ports() { + let Some(export_bounding_box) = export_click_target.bounding_box() else { log::error!("Could not get export bounding box in load_modify_import_export"); continue; }; - remove_imports_exports.insert_output_port_at_center(*import_index, (import_bounding_box[0] + import_bounding_box[1]) / 2. + DVec2::new(-16., 0.)); + let reorder_export_center = (export_bounding_box[0] + export_bounding_box[1]) / 2. + DVec2::new(12., 0.); + let remove_export_center = reorder_export_center + DVec2::new(12., 0.); + + let reorder_export = ClickTarget::new(Subpath::new_rect(reorder_export_center - DVec2::new(3., 4.), reorder_export_center + DVec2::new(3., 4.)), 0.); + let remove_export = ClickTarget::new(Subpath::new_rect(remove_export_center - DVec2::new(8., 8.), remove_export_center + DVec2::new(8., 8.)), 0.); + + reorder_imports_exports.insert_custom_input_port(*export_index, reorder_export); + remove_imports_exports.insert_custom_input_port(*export_index, remove_export); } } @@ -2055,7 +2078,7 @@ impl NodeNetworkInterface { network_metadata.transient_metadata.modify_import_export = TransientMetadata::Loaded(ModifyImportExportClickTarget { add_import_export, remove_imports_exports, - move_imports_exports: Ports::new(), + reorder_imports_exports, }); } @@ -2095,7 +2118,6 @@ impl NodeNetworkInterface { let node_graph_to_viewport = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport; // TODO: Eventually replace node graph top right with the footprint when trying to get the network edge distance let node_graph_top_right = network_metadata.persistent_metadata.navigation_metadata.node_graph_top_right; - log::debug!("Node graph top right is {node_graph_top_right:?}"); let target_exports_distance = node_graph_to_viewport.inverse().transform_point2(DVec2::new( node_graph_top_right.x - EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP as f64, node_graph_top_right.y + EXPORTS_TO_TOP_EDGE_PIXEL_GAP as f64, @@ -2119,7 +2141,6 @@ impl NodeNetworkInterface { log::error!("Could not get current network in load_export_ports"); return; }; - log::debug!("Setting rounded network edge distance to {network_edge_distance:?}"); network_metadata.transient_metadata.rounded_network_edge_distance = TransientMetadata::Loaded(network_edge_distance); } @@ -2740,7 +2761,7 @@ impl NodeNetworkInterface { .add_import_export .click_targets() .chain(modify_import_export_click_targets.remove_imports_exports.click_targets()) - .chain(modify_import_export_click_targets.move_imports_exports.click_targets()) + .chain(modify_import_export_click_targets.reorder_imports_exports.click_targets()) { let mut remove_string = String::new(); let _ = click_target.subpath().subpath_to_svg(&mut remove_string, DAffine2::IDENTITY); @@ -3346,18 +3367,34 @@ impl NodeNetworkInterface { return; }; + // Disconnect the removed export, and handle connections to the node which had its output removed self.disconnect_input(&InputConnector::Export(export_index), network_path); + let number_of_outputs = self.number_of_outputs(&parent_id, &encapsulating_network_path); + for shifted_export in export_index..number_of_outputs { + let Some(encapsulating_outward_wires) = self.outward_wires(&encapsulating_network_path) else { + log::error!("Could not get outward wires in remove_export"); + return; + }; + let Some(downstream_connections_for_shifted_export) = encapsulating_outward_wires.get(&OutputConnector::node(parent_id, shifted_export)).cloned() else { + log::error!("Could not get downstream connections for shifted export in remove_export"); + return; + }; + for downstream_connection in downstream_connections_for_shifted_export { + self.disconnect_input(&downstream_connection, &encapsulating_network_path); + if shifted_export != export_index { + self.create_wire(&OutputConnector::node(parent_id, shifted_export - 1), &downstream_connection, &encapsulating_network_path); + } + } + } let Some(network) = self.network_mut(network_path) else { log::error!("Could not get nested network in add_export"); return; }; - network.exports.remove(export_index); self.transaction_modified(); - // There will not be an encapsulating node if the network is the document network let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { log::error!("Could not get encapsulating node metadata in remove_export"); return; @@ -3455,6 +3492,51 @@ impl NodeNetworkInterface { self.unload_modify_import_export(network_path); } + /// The end index is before the export is removed, so moving to the end is the length of the current exports + pub fn reorder_export(&mut self, start_index: usize, mut end_index: usize, network_path: &[NodeId]) { + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Could not reorder export for document network"); + return; + }; + + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in reorder_export"); + return; + }; + if end_index > start_index { + end_index -= 1; + } + let export = network.exports.remove(start_index); + network.exports.insert(end_index, export); + + self.transaction_modified(); + + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating network_metadata in reorder_export"); + return; + }; + + let name = encapsulating_node_metadata.persistent_metadata.output_names.remove(start_index); + encapsulating_node_metadata.persistent_metadata.output_names.insert(end_index, name); + + // TODO: Reorder the wires to the node output in the encapsulating network + + // Update the metadata for the encapsulating network + self.unload_outward_wires(&encapsulating_network_path); + + // Update the metadata for the current network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); + self.unload_stack_dependents(network_path); + } + + /// The end index is before the import is removed, so moving to the end is the length of the current imports + pub fn reorder_import(&mut self, start_index: usize, mut end_index: usize, network_path: &[NodeId]) { + log::debug!("Reordering import from {start_index} to {end_index}"); + } + /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) { let Some(network) = self.network_mut(network_path) else { @@ -5703,7 +5785,7 @@ pub struct ModifyImportExportClickTarget { // Subtract icon that appears when hovering over an import/export pub remove_imports_exports: Ports, // Grip drag icon that appears when hovering over an import/export - pub move_imports_exports: Ports, + pub reorder_imports_exports: Ports, } #[derive(Debug, Clone)] diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 9dc214e040..a990dafb01 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -440,7 +440,7 @@ {/if} -
+
+
-

{outputMetadata.name}

+

{outputMetadata.name}

{/each} + {#if $nodeGraph.reorderImportIndex} + {@const position = { + x: Number($nodeGraph.imports[0].position.x), + y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24, + }} +
+ {/if} {#if $nodeGraph.addImport !== undefined}
+
-

{inputMetadata.name}

+

{inputMetadata.name}

{/each} + {#if $nodeGraph.reorderExportIndex !== undefined} + {@const position = { + x: Number($nodeGraph.exports[0].position.x), + y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24, + }} + {console.log("position:", position)} +
+ {/if} {#if $nodeGraph.addExport !== undefined}
{ + update((state) => { + state.reorderImportIndex = updateImportReorderIndex.importIndex; + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateExportReorderIndex, (updateExportReorderIndex) => { + update((state) => { + state.reorderExportIndex = updateExportReorderIndex.exportIndex; + return state; + }); + }); editor.subscriptions.subscribeJsMessage(UpdateImportsExports, (updateImportsExports) => { update((state) => { state.imports = updateImportsExports.imports; diff --git a/frontend/src/wasm-communication/messages.ts b/frontend/src/wasm-communication/messages.ts index 510e53e271..72bb247f9d 100644 --- a/frontend/src/wasm-communication/messages.ts +++ b/frontend/src/wasm-communication/messages.ts @@ -73,6 +73,14 @@ export class UpdateInSelectedNetwork extends JsMessage { readonly inSelectedNetwork!: boolean; } +export class UpdateImportReorderIndex extends JsMessage { + readonly importIndex!: number | undefined; +} + +export class UpdateExportReorderIndex extends JsMessage { + readonly exportIndex!: number | undefined; +} + const LayerWidths = Transform(({ obj }) => obj.layerWidths); const ChainWidths = Transform(({ obj }) => obj.chainWidths); const HasLeftInputWire = Transform(({ obj }) => obj.hasLeftInputWire); @@ -1608,6 +1616,8 @@ export const messageMakers: Record = { UpdateImportsExports, UpdateInputHints, UpdateInSelectedNetwork, + UpdateExportReorderIndex, + UpdateImportReorderIndex, UpdateLayersPanelOptionsLayout, UpdateLayerWidths, UpdateMenuBarLayout, From ac8e215d389b5e1defe0f47895e1c553225d259b Mon Sep 17 00:00:00 2001 From: Adam G Date: Mon, 2 Dec 2024 15:43:12 -0800 Subject: [PATCH 07/19] Continue migrating properties --- .../node_graph/document_node_definitions.rs | 59 ++++++++++++++++++- .../document/node_graph/node_properties.rs | 45 -------------- 2 files changed, 56 insertions(+), 48 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 8c4ede29d3..67c140743f 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 @@ -1954,7 +1954,12 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".into(), "Brightness".into(), "Contrast".into(), "Use Classic".into()], + input_properties: vec![ + PropertiesRow::with_override("Image", WidgetOverride::Hidden), + PropertiesRow::with_override("Brightness", WidgetOverride::Custom("brightness".to_string())), + PropertiesRow::with_override("Brightness", WidgetOverride::Custom("contrast".to_string())), + "Use Classic".into(), + ], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1977,7 +1982,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Image".into(), "Curve".into()], + input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::Hidden), "Curve".into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -2577,6 +2582,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { + // TODO: Store information for input overrides in the node macro input_properties: fields.iter().map(|f| f.name.into()).collect(), output_names: vec![output_type.to_string()], has_primary_output: true, @@ -2925,7 +2931,54 @@ fn static_input_properties() -> HashMap Some(use_classic), + _ => None, + }) + .unwrap_or(false); + let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) }; + let brightness = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2), + true, + ); + Ok(vec![brightness.into()]) + }), + ); + map.insert( + "contrast".to_string(), + Box::new(|node_id, index, context| { + let (document_node, input_name) = node_properties::query_node_and_input_name(node_id, index, context)?; + let is_use_classic = document_node + .inputs + .iter() + .find_map(|input| match input.as_value() { + Some(&TaggedValue::Bool(use_classic)) => Some(use_classic), + _ => None, + }) + .unwrap_or(false); + let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) }; + let contrast = node_properties::number_widget( + document_node, + node_id, + index, + input_name, + NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2), + true, + ); + Ok(vec![contrast.into()]) + }), + ); map } 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 1bd820e62e..9f7f6f20b4 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1178,51 +1178,6 @@ pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContex )) } -pub(crate) fn brightness_contrast_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let is_use_classic = match &document_node.inputs[3].as_value() { - Some(&TaggedValue::Bool(value)) => value, - _ => false, - }; - let ((b_min, b_max), (c_min, c_max)) = if is_use_classic { ((-100., 100.), (-100., 100.)) } else { ((-100., 150.), (-50., 100.)) }; - - let brightness = number_widget( - document_node, - node_id, - 1, - "Brightness", - NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2), - true, - ); - let contrast = number_widget( - document_node, - node_id, - 2, - "Contrast", - NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2), - true, - ); - let use_classic = bool_widget(document_node, node_id, 3, "Use Classic", CheckboxInput::default(), true); - - vec![ - LayoutGroup::Row { widgets: brightness }, - LayoutGroup::Row { widgets: contrast }, - LayoutGroup::Row { widgets: use_classic }, - ] -} - -pub(crate) fn curves_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let curves = curves_widget(document_node, node_id, 1, "Curve", true); - - vec![curves] -} - -pub(crate) fn _blur_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let radius = number_widget(document_node, node_id, 1, "Radius", NumberInput::default().min(0.).max(20.).int(), true); - let sigma = number_widget(document_node, node_id, 2, "Sigma", NumberInput::default().min(0.).max(10000.), true); - - vec![LayoutGroup::Row { widgets: radius }, LayoutGroup::Row { widgets: sigma }] -} - pub(crate) fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let fill_index = 1; let stroke_index = 2; From 3a3b1d28f722e13d6483599fc2d29b9f5e994c81 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 3 Dec 2024 23:46:18 -0800 Subject: [PATCH 08/19] WIP: Improve reorder exports --- .../node_graph/node_graph_message_handler.rs | 2 +- .../utility_types/network_interface.rs | 51 ++++++++++++++++++- frontend/src/components/views/Graph.svelte | 1 - 3 files changed, 50 insertions(+), 4 deletions(-) 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 44d6c08728..107bc0eb4d 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 @@ -11,7 +11,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{ self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, }; -use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry, SelectedNodes}; +use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; 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 ae04163eea..e7bbbfb07f 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3520,11 +3520,58 @@ impl NodeNetworkInterface { let name = encapsulating_node_metadata.persistent_metadata.output_names.remove(start_index); encapsulating_node_metadata.persistent_metadata.output_names.insert(end_index, name); - // TODO: Reorder the wires to the node output in the encapsulating network - // Update the metadata for the encapsulating network self.unload_outward_wires(&encapsulating_network_path); + // Node input at the start index is now at the end index + let Some(move_to_end_index) = self + .outward_wires(&encapsulating_network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, start_index))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_export"); + return; + }; + // Node inputs above the start index should be shifted down one + let last_output_index = self.number_of_outputs(&parent_id, &encapsulating_network_path) - 1; + for shift_output_down in (start_index + 1)..=last_output_index { + let Some(outward_wires) = self + .outward_wires(&encapsulating_network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, shift_output_down))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_export"); + return; + }; + for downstream_connection in &outward_wires { + log::debug!("Shifting downstream_connection {downstream_connection:?} down to {}", shift_output_down - 1); + self.disconnect_input(downstream_connection, &encapsulating_network_path); + self.create_wire(&OutputConnector::node(parent_id, shift_output_down - 1), downstream_connection, &encapsulating_network_path); + } + } + // Node inputs at or above the end index should be shifted up one + for shift_output_up in end_index..=last_output_index { + let Some(outward_wires) = self + .outward_wires(&encapsulating_network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, shift_output_up))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_export"); + return; + }; + for downstream_connection in &outward_wires { + log::debug!("Shifting downstream_connection {downstream_connection:?} up to {}", shift_output_up + 1); + self.disconnect_input(downstream_connection, &encapsulating_network_path); + self.create_wire(&OutputConnector::node(parent_id, shift_output_up + 1), downstream_connection, &encapsulating_network_path); + } + } + + // Move the connections to the moved export after all other ones have been shifted + for downstream_connection in &move_to_end_index { + self.disconnect_input(downstream_connection, &encapsulating_network_path); + self.create_wire(&OutputConnector::node(parent_id, end_index), downstream_connection, &encapsulating_network_path); + } + // Update the metadata for the current network self.unload_outward_wires(network_path); self.unload_import_export_ports(network_path); diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index a990dafb01..c65f18cd3f 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -507,7 +507,6 @@ x: Number($nodeGraph.exports[0].position.x), y: Number($nodeGraph.exports[0].position.y) + Number($nodeGraph.reorderExportIndex) * 24, }} - {console.log("position:", position)}
{/if} {#if $nodeGraph.addExport !== undefined} From 6d894eec26554cf478d9bc4f737a24b8f3706dfc Mon Sep 17 00:00:00 2001 From: Adam G Date: Wed, 4 Dec 2024 11:07:16 -0800 Subject: [PATCH 09/19] Automatically populate all input properties for sub networks --- .../node_graph/document_node_definitions.rs | 78 ++++++++++++++++--- .../utility_types/network_interface.rs | 10 ++- 2 files changed, 78 insertions(+), 10 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 67c140743f..13fbeaf068 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 @@ -50,15 +50,19 @@ impl NodePropertiesContext<'_> { log::error!("Could not get input properties row in call_widget_override"); return None; }; - let Some(widget_override_lambda) = input_properties_row.widget_override.as_ref().and_then(|widget_override| INPUT_OVERRIDES.get(widget_override)) else { - log::error!("Could not get widget override lambda in call_widget_override"); - return None; - }; - widget_override_lambda(*node_id, index, self) - .map_err(|error| { - log::error!("Error in widget override lambda: {}", error); - }) - .ok() + if let Some(widget_override) = &input_properties_row.widget_override { + let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else { + log::error!("Could not get widget override lambda in call_widget_override"); + return None; + }; + widget_override_lambda(*node_id, index, self) + .map_err(|error| { + log::error!("Error in widget override lambda: {}", error); + }) + .ok() + } else { + None + } } } @@ -3014,6 +3018,62 @@ impl DocumentNodeDefinition { } }); + //Ensure that the input properties are initialized for every Document Node input for every node + fn populate_input_properties(node_template: &mut NodeTemplate, mut path: Vec) { + if let Some(current_node) = path.pop() { + let DocumentNodeImplementation::Network(template_network) = &node_template.document_node.implementation else { + log::error!("Template network should always exist"); + return; + }; + let Some(nested_network) = template_network.nested_network(&path) else { + log::error!("Nested network should exist for path"); + return; + }; + let Some(input_length) = nested_network.nodes.get(¤t_node).map(|node| node.inputs.len()) else { + log::error!("Could not get current node in nested network"); + return; + }; + let Some(template_network_metadata) = &mut node_template.persistent_node_metadata.network_metadata else { + log::error!("Template should have metadata if it has network implementation"); + return; + }; + let Some(nested_network_metadata) = template_network_metadata.nested_metadata_mut(&path) else { + log::error!("Path is not valid for network"); + return; + }; + let Some(nested_node_metadata) = nested_network_metadata.persistent_metadata.node_metadata.get_mut(¤t_node) else { + log::error!("Path is not valid for network"); + return; + }; + nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default); + + //Recurse over all sub nodes if the current node is a network implementation + let mut current_path = path.clone(); + current_path.push(current_node); + let DocumentNodeImplementation::Network(template_network) = &node_template.document_node.implementation else { + log::error!("Template network should always exist"); + return; + }; + if let Some(current_nested_network) = template_network.nested_network(¤t_path) { + for sub_node_id in current_nested_network.nodes.keys().cloned().collect::>() { + let mut sub_path = current_path.clone(); + sub_path.push(sub_node_id); + populate_input_properties(node_template, sub_path); + } + }; + } else { + // Base case + let input_len = node_template.document_node.inputs.len(); + node_template.persistent_node_metadata.input_properties.resize_with(input_len, PropertiesRow::default); + if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation { + for sub_node_id in node_template_network.nodes.keys().cloned().collect::>() { + populate_input_properties(node_template, vec![sub_node_id]); + } + } + } + } + populate_input_properties(&mut template, Vec::new()); + // Set the reference to the node definition template.persistent_node_metadata.reference = Some(self.identifier.to_string()); template 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 e7bbbfb07f..b03fd28422 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -5887,7 +5887,8 @@ pub enum WidgetOverride { Custom(String), } -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] +// TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. /// If the string is empty/DNE, then it uses the type. @@ -5899,6 +5900,12 @@ pub struct PropertiesRow { pub widget_override: Option, } +impl Default for PropertiesRow { + fn default() -> Self { + "".into() + } +} + impl PartialEq for PropertiesRow { fn eq(&self, other: &Self) -> bool { self.input_data == other.input_data && self.widget_override == other.widget_override @@ -5952,6 +5959,7 @@ pub struct DocumentNodePersistentMetadata { #[serde(default)] pub display_name: String, /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. + /// Must match the length of node inputs pub input_properties: Vec, /// A node can have a fully custom properties panel. For example to display a single string, or if interdependent properties are needed pub output_names: Vec, From b6f8b851a0e6556d51d0b029cd649206923ab36c Mon Sep 17 00:00:00 2001 From: Adam G Date: Wed, 4 Dec 2024 12:11:35 -0800 Subject: [PATCH 10/19] Complete reorder import and export --- .../utility_types/network_interface.rs | 93 ++++++++++++++++++- frontend/src/components/views/Graph.svelte | 28 +++--- 2 files changed, 104 insertions(+), 17 deletions(-) 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 b03fd28422..45523153ef 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3522,6 +3522,7 @@ impl NodeNetworkInterface { // Update the metadata for the encapsulating network self.unload_outward_wires(&encapsulating_network_path); + self.unload_stack_dependents(&encapsulating_network_path); // Node input at the start index is now at the end index let Some(move_to_end_index) = self @@ -3544,13 +3545,12 @@ impl NodeNetworkInterface { return; }; for downstream_connection in &outward_wires { - log::debug!("Shifting downstream_connection {downstream_connection:?} down to {}", shift_output_down - 1); self.disconnect_input(downstream_connection, &encapsulating_network_path); self.create_wire(&OutputConnector::node(parent_id, shift_output_down - 1), downstream_connection, &encapsulating_network_path); } } // Node inputs at or above the end index should be shifted up one - for shift_output_up in end_index..=last_output_index { + for shift_output_up in (end_index..last_output_index).rev() { let Some(outward_wires) = self .outward_wires(&encapsulating_network_path) .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(parent_id, shift_output_up))) @@ -3560,7 +3560,6 @@ impl NodeNetworkInterface { return; }; for downstream_connection in &outward_wires { - log::debug!("Shifting downstream_connection {downstream_connection:?} up to {}", shift_output_up + 1); self.disconnect_input(downstream_connection, &encapsulating_network_path); self.create_wire(&OutputConnector::node(parent_id, shift_output_up + 1), downstream_connection, &encapsulating_network_path); } @@ -3581,7 +3580,93 @@ impl NodeNetworkInterface { /// The end index is before the import is removed, so moving to the end is the length of the current imports pub fn reorder_import(&mut self, start_index: usize, mut end_index: usize, network_path: &[NodeId]) { - log::debug!("Reordering import from {start_index} to {end_index}"); + let mut encapsulating_network_path = network_path.to_vec(); + let Some(parent_id) = encapsulating_network_path.pop() else { + log::error!("Could not reorder import for document network"); + return; + }; + + let Some(encapsulating_network) = self.network_mut(&encapsulating_network_path) else { + log::error!("Could not get nested network in reorder_import"); + return; + }; + let Some(encapsulating_node) = encapsulating_network.nodes.get_mut(&parent_id) else { + log::error!("Could not get encapsulating node in reorder_import"); + return; + }; + + if end_index > start_index { + end_index -= 1; + } + let import = encapsulating_node.inputs.remove(start_index); + encapsulating_node.inputs.insert(end_index, import); + + self.transaction_modified(); + + let Some(encapsulating_node_metadata) = self.node_metadata_mut(&parent_id, &encapsulating_network_path) else { + log::error!("Could not get encapsulating network_metadata in reorder_import"); + return; + }; + + let properties_row = encapsulating_node_metadata.persistent_metadata.input_properties.remove(start_index); + encapsulating_node_metadata.persistent_metadata.input_properties.insert(end_index, properties_row); + + // Update the metadata for the outer network + self.unload_outward_wires(&encapsulating_network_path); + self.unload_stack_dependents(&encapsulating_network_path); + + // Node input at the start index is now at the end index + let Some(move_to_end_index) = self + .outward_wires(network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(start_index))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_import"); + return; + }; + // Node inputs above the start index should be shifted down one + let last_import_index = self.number_of_imports(network_path) - 1; + for shift_output_down in (start_index + 1)..=last_import_index { + let Some(outward_wires) = self + .outward_wires(network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(shift_output_down))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_import"); + return; + }; + for downstream_connection in &outward_wires { + self.disconnect_input(downstream_connection, &network_path); + self.create_wire(&OutputConnector::Import(shift_output_down - 1), downstream_connection, &network_path); + } + } + // Node inputs at or above the end index should be shifted up one + for shift_output_up in (end_index..last_import_index).rev() { + let Some(outward_wires) = self + .outward_wires(&network_path) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(shift_output_up))) + .cloned() + else { + log::error!("Could not get outward wires in reorder_import"); + return; + }; + for downstream_connection in &outward_wires { + self.disconnect_input(downstream_connection, &network_path); + self.create_wire(&OutputConnector::Import(shift_output_up + 1), downstream_connection, &network_path); + } + } + + // Move the connections to the moved export after all other ones have been shifted + for downstream_connection in &move_to_end_index { + self.disconnect_input(downstream_connection, &network_path); + self.create_wire(&OutputConnector::Import(end_index), downstream_connection, &network_path); + } + + // Update the metadata for the current network + self.unload_outward_wires(network_path); + self.unload_import_export_ports(network_path); + self.unload_modify_import_export(network_path); + self.unload_stack_dependents(network_path); } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index c65f18cd3f..12fe0da598 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -448,16 +448,16 @@ /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ }} /> -
+

{outputMetadata.name}

{/each} - {#if $nodeGraph.reorderImportIndex} + {#if $nodeGraph.reorderImportIndex !== undefined} {@const position = { x: Number($nodeGraph.imports[0].position.x), y: Number($nodeGraph.imports[0].position.y) + Number($nodeGraph.reorderImportIndex) * 24, }} -
+
{/if} {#if $nodeGraph.addImport !== undefined}
@@ -490,16 +490,18 @@ {/if} -
-
- { - /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ - }} - /> -
+ {#if $nodeGraph.addExport !== undefined} +
+
+ { + /* Button is purely visual, clicking is handled in NodeGraphMessage::PointerDown */ + }} + /> +
+ {/if}

{inputMetadata.name}

{/each} {#if $nodeGraph.reorderExportIndex !== undefined} From be4b2dc906f12e4ffccd938953aababd1635cfbf Mon Sep 17 00:00:00 2001 From: Adam G Date: Thu, 5 Dec 2024 18:24:01 -0800 Subject: [PATCH 11/19] Add widget override to node macro --- .../node_graph/document_node_definitions.rs | 16 +++- .../utility_types/network_interface.rs | 3 - node-graph/gcore/src/registry.rs | 13 ++- node-graph/gcore/src/vector/vector_nodes.rs | 1 + node-graph/node-macro/src/codegen.rs | 28 +++++-- node-graph/node-macro/src/parsing.rs | 84 +++++++++++++++---- 6 files changed, 115 insertions(+), 30 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 13fbeaf068..f94fe97b72 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 @@ -2562,9 +2562,9 @@ fn static_nodes() -> Vec { let exposed = if index == 0 { *ty != fn_type!(()) } else { field.exposed }; match field.value_source { - ValueSource::None => {} - ValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), - ValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), + RegistryValueSource::None => {} + RegistryValueSource::Default(data) => return NodeInput::value(TaggedValue::from_primitive_string(data, ty).unwrap_or(TaggedValue::None), exposed), + RegistryValueSource::Scope(data) => return NodeInput::scope(Cow::Borrowed(data)), }; if let Some(type_default) = TaggedValue::from_type(ty) { @@ -2587,7 +2587,15 @@ fn static_nodes() -> Vec { }, persistent_node_metadata: DocumentNodePersistentMetadata { // TODO: Store information for input overrides in the node macro - input_properties: fields.iter().map(|f| f.name.into()).collect(), + input_properties: fields + .iter() + .map(|f| match f.widget_override { + RegistryWidgetOverride::None => f.name.into(), + RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, WidgetOverride::Hidden), + RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, WidgetOverride::String(str.to_string())), + RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, WidgetOverride::Custom(str.to_string())), + }) + .collect(), output_names: vec![output_type.to_string()], has_primary_output: true, locked: false, 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 45523153ef..e3ae09156e 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -5975,9 +5975,6 @@ pub enum WidgetOverride { // TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { - /// Input/Output names may not be the same length as the number of inputs/outputs. They are the same as the nested networks Imports/Exports. - /// If the string is empty/DNE, then it uses the type. - // pub input_name: String, /// A general datastore than can store key value pairs of any types for any input pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 04c5ec6e13..897bb143d6 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -42,15 +42,24 @@ pub struct NodeMetadata { #[derive(Clone, Debug)] pub struct FieldMetadata { pub name: &'static str, + pub widget_override: RegistryWidgetOverride, pub exposed: bool, - pub value_source: ValueSource, + pub value_source: RegistryValueSource, pub number_min: Option, pub number_max: Option, pub number_mode_range: Option<(f64, f64)>, } #[derive(Clone, Debug)] -pub enum ValueSource { +pub enum RegistryWidgetOverride { + None, + Hidden, + String(&'static str), + Custom(&'static str), +} + +#[derive(Clone, Debug)] +pub enum RegistryValueSource { None, Default(&'static str), Scope(&'static str), diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index bfe3fb2028..53fa00834d 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -50,6 +50,7 @@ async fn assign_colors( Footprint -> GraphicGroup, Footprint -> VectorData, )] + #[widget(ParsedWidgetOverride::Hidden)] vector_group: impl Node, #[default(true)] fill: bool, stroke: bool, diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index beb401f2dc..7832aa423e 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -77,21 +77,36 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result ty.clone(), ParsedField::Node { output_type, input_type, .. } => match parsed.is_async { true => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output: core::future::Future + #graphene_core::WasmNotSend>), - false => parse_quote!(&'n impl #graphene_core::Node<'n, #input_type, Output = #output_type>), }, }) .collect(); + let widget_override: Vec<_> = fields + .iter() + .map(|field| { + let parsed_widget_override = match field { + ParsedField::Regular { widget_override, .. } => widget_override, + ParsedField::Node { widget_override, .. } => widget_override, + }; + match parsed_widget_override { + ParsedWidgetOverride::None => quote!(RegistryWidgetOverride::None), + ParsedWidgetOverride::Hidden => quote!(RegistryWidgetOverride::Hidden), + ParsedWidgetOverride::String(lit_str) => quote!(RegistryWidgetOverride::String(#lit_str)), + ParsedWidgetOverride::Custom(lit_str) => quote!(RegistryWidgetOverride::Custom(#lit_str)), + } + }) + .collect(); + let value_sources: Vec<_> = fields .iter() .map(|field| match field { ParsedField::Regular { value_source, .. } => match value_source { - ValueSource::Default(data) => quote!(ValueSource::Default(stringify!(#data))), - ValueSource::Scope(data) => quote!(ValueSource::Scope(#data)), - _ => quote!(ValueSource::None), + ParsedValueSource::Default(data) => quote!(RegistryValueSource::Default(stringify!(#data))), + ParsedValueSource::Scope(data) => quote!(RegistryValueSource::Scope(#data)), + _ => quote!(RegistryValueSource::None), }, - _ => quote!(ValueSource::None), + _ => quote!(RegistryValueSource::None), }) .collect(); @@ -228,7 +243,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result syn::Result { + // Parse the full path (e.g., ParsedWidgetOverride::Hidden) + let path: Path = input.parse()?; + + // Ensure the path starts with `ParsedWidgetOverride` + if path.segments.len() == 2 && path.segments[0].ident == "ParsedWidgetOverride" { + let variant = &path.segments[1].ident; + + match variant.to_string().as_str() { + "Hidden" => Ok(ParsedWidgetOverride::Hidden), + "String" => { + input.parse::()?; + let lit: LitStr = input.parse()?; + Ok(ParsedWidgetOverride::String(lit)) + } + "Custom" => { + input.parse::()?; + let lit: LitStr = input.parse()?; + Ok(ParsedWidgetOverride::Custom(lit)) + } + _ => Err(syn::Error::new(variant.span(), "Unknown ParsedWidgetOverride variant")), + } + } else { + Err(syn::Error::new(input.span(), "Expected ParsedWidgetOverride::")) + } + } +} + #[derive(Debug)] pub(crate) enum ParsedField { Regular { pat_ident: PatIdent, name: Option, + widget_override: ParsedWidgetOverride, ty: Type, exposed: bool, - value_source: ValueSource, + value_source: ParsedValueSource, number_min: Option, number_max: Option, number_mode_range: Option, @@ -66,6 +109,7 @@ pub(crate) enum ParsedField { Node { pat_ident: PatIdent, name: Option, + widget_override: ParsedWidgetOverride, input_type: Type, output_type: Type, implementations: Punctuated, @@ -341,13 +385,21 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul .map(|attr| attr.parse_args().map_err(|e| Error::new_spanned(attr, format!("Invalid `name` value for argument '{}': {}", ident, e)))) .transpose()?; + let widget_override = extract_attribute(attrs, "widget") + .map(|attr| { + attr.parse_args() + .map_err(|e| Error::new_spanned(attr, format!("Invalid `widget override` value for argument '{}': {}", ident, e))) + }) + .transpose()? + .unwrap_or_default(); + let exposed = extract_attribute(attrs, "expose").is_some(); let value_source = match (default_value, scope) { (Some(_), Some(_)) => return Err(Error::new_spanned(&pat_ident, "Cannot have both `default` and `scope` attributes")), - (Some(default_value), _) => ValueSource::Default(default_value), - (_, Some(scope)) => ValueSource::Scope(scope), - _ => ValueSource::None, + (Some(default_value), _) => ParsedValueSource::Default(default_value), + (_, Some(scope)) => ParsedValueSource::Scope(scope), + _ => ParsedValueSource::None, }; let number_min = extract_attribute(attrs, "min") @@ -388,7 +440,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul let (input_type, output_type) = node_input_type .zip(node_output_type) .ok_or_else(|| Error::new_spanned(&ty, "Invalid Node type. Expected `impl Node`"))?; - if !matches!(&value_source, ValueSource::None) { + if !matches!(&value_source, ParsedValueSource::None) { return Err(Error::new_spanned(&ty, "No default values for `impl Node` allowed")); } let implementations = extract_attribute(attrs, "implementations") @@ -399,6 +451,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul Ok(ParsedField::Node { pat_ident, name, + widget_override, input_type, output_type, implementations, @@ -411,6 +464,7 @@ fn parse_field(pat_ident: PatIdent, ty: Type, attrs: &[Attribute]) -> syn::Resul Ok(ParsedField::Regular { pat_ident, name, + widget_override, exposed, number_min, number_max, @@ -531,11 +585,11 @@ mod tests { assert_eq!(p_name, e_name); assert_eq!(p_exp, e_exp); match (p_default, e_default) { - (ValueSource::None, ValueSource::None) => {} - (ValueSource::Default(p), ValueSource::Default(e)) => { + (ParsedValueSource::None, ParsedValueSource::None) => {} + (ParsedValueSource::Default(p), ParsedValueSource::Default(e)) => { assert_eq!(p.to_token_stream().to_string(), e.to_token_stream().to_string()); } - (ValueSource::Scope(p), ValueSource::Scope(e)) => { + (ParsedValueSource::Scope(p), ParsedValueSource::Scope(e)) => { assert_eq!(p.value(), e.value()); } _ => panic!("Mismatched default values"), @@ -601,7 +655,7 @@ mod tests { name: None, ty: parse_quote!(f64), exposed: false, - value_source: ValueSource::None, + value_source: ParsedValueSource::None, number_min: None, number_max: None, number_mode_range: None, @@ -661,7 +715,7 @@ mod tests { name: None, ty: parse_quote!(DVec2), exposed: false, - value_source: ValueSource::None, + value_source: ParsedValueSource::None, number_min: None, number_max: None, number_mode_range: None, @@ -711,7 +765,7 @@ mod tests { name: None, ty: parse_quote!(f64), exposed: false, - value_source: ValueSource::Default(quote!(50.)), + value_source: ParsedValueSource::Default(quote!(50.)), number_min: None, number_max: None, number_mode_range: None, @@ -759,7 +813,7 @@ mod tests { name: None, ty: parse_quote!(f64), exposed: false, - value_source: ValueSource::None, + value_source: ParsedValueSource::None, number_min: None, number_max: None, number_mode_range: None, @@ -818,7 +872,7 @@ mod tests { name: None, ty: parse_quote!(f64), exposed: false, - value_source: ValueSource::None, + value_source: ParsedValueSource::None, number_min: Some(parse_quote!(-500.)), number_max: Some(parse_quote!(500.)), number_mode_range: Some(parse_quote!((0., 100.))), @@ -866,7 +920,7 @@ mod tests { name: None, ty: parse_quote!(String), exposed: true, - value_source: ValueSource::None, + value_source: ParsedValueSource::None, number_min: None, number_max: None, number_mode_range: None, From 9f739082585f8a08703a71304aa16923b874f6ed Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 8 Dec 2024 22:43:19 -0800 Subject: [PATCH 12/19] Migrate assign colors to input based properties --- .../node_graph/document_node_definitions.rs | 32 +++++++-- .../document/node_graph/node_properties.rs | 65 +++++-------------- node-graph/gcore/src/vector/vector_nodes.rs | 8 +-- 3 files changed, 48 insertions(+), 57 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 f94fe97b72..732ad66e33 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 @@ -40,12 +40,6 @@ pub struct NodePropertiesContext<'a> { impl NodePropertiesContext<'_> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option> { - //let current_override = //Get mutable reference from transient metadata - //let mut widget_override = std::mem::replace(&mut WidgetOverrideLambda(Box::new()), current_override); - // let layout = widget_override.0(node_id, context); - //let current_override = //Get mutable reference from transient metadata (now empty) - //let empty_widget_override = std::mem::replace(&mut widget_override, current_override) // Put back the taken override - // Some(layout) let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { log::error!("Could not get input properties row in call_widget_override"); return None; @@ -2991,6 +2985,32 @@ fn static_input_properties() -> HashMap(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> { + let network = context.network_interface.network(context.selection_network_path).ok_or("network not found in get_document_node")?; + network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node")) +} + pub fn query_node_and_input_name<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str), String> { - let network = context - .network_interface - .network(context.selection_network_path) - .ok_or("network not found in query_node_and_input_name")?; - let document_node = network.nodes.get(&node_id).ok_or("node not found in query_node_and_input_name")?; + let document_node = get_document_node(node_id, context)?; let input_name = context .network_interface .input_name(&node_id, input_index, context.selection_network_path) @@ -1143,11 +1144,7 @@ pub fn query_node_and_input_name<'a>(node_id: NodeId, input_index: usize, contex } pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> { - let network = context - .network_interface - .network(context.selection_network_path) - .ok_or("network not found in query_noise_pattern_state")?; - let document_node = network.nodes.get(&node_id).ok_or("node not found in query_noise_pattern_state")?; + let document_node = get_document_node(node_id, context)?; let current_noise_type = document_node.inputs.iter().find_map(|input| match input.as_value() { Some(&TaggedValue::NoiseType(noise_type)) => Some(noise_type), _ => None, @@ -1178,44 +1175,17 @@ pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContex )) } -pub(crate) fn assign_colors_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let fill_index = 1; - let stroke_index = 2; - let gradient_index = 3; - let reverse_index = 4; +pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesContext) -> Result { + let document_node = get_document_node(node_id, context)?; + // This is safe since the node is a proto node and the implementation cannot be changed. let randomize_index = 5; - let seed_index = 6; - let repeat_every_index = 7; - - let fill_row = bool_widget(document_node, node_id, fill_index, "Fill", CheckboxInput::default(), true); - let stroke_row = bool_widget(document_node, node_id, stroke_index, "Stroke", CheckboxInput::default(), true); - let gradient_row = color_widget(document_node, node_id, gradient_index, "Gradient", ColorButton::default().allow_none(false), true); - let reverse_row = bool_widget(document_node, node_id, reverse_index, "Reverse", CheckboxInput::default(), true); - let randomize_enabled = if let Some(&TaggedValue::Bool(randomize_enabled)) = &document_node.inputs[randomize_index].as_value() { - randomize_enabled - } else { - false - }; - let randomize_row = bool_widget(document_node, node_id, randomize_index, "Randomize", CheckboxInput::default(), true); - let seed_row = number_widget(document_node, node_id, seed_index, "Seed", NumberInput::default().min(0.).int().disabled(!randomize_enabled), true); - let repeat_every_row = number_widget( - document_node, - node_id, - repeat_every_index, - "Repeat Every", - NumberInput::default().min(0.).int().disabled(randomize_enabled), - true, - ); - - vec![ - LayoutGroup::Row { widgets: fill_row }, - LayoutGroup::Row { widgets: stroke_row }, - gradient_row, - LayoutGroup::Row { widgets: reverse_row }, - LayoutGroup::Row { widgets: randomize_row }, - LayoutGroup::Row { widgets: seed_row }, - LayoutGroup::Row { widgets: repeat_every_row }, - ] + Ok( + if let Some(&TaggedValue::Bool(randomize_enabled)) = &document_node.inputs.get(randomize_index).and_then(|input| input.as_value()) { + randomize_enabled + } else { + false + }, + ) } pub(crate) fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { @@ -2124,6 +2094,7 @@ pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _c pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup { let mut layout = Vec::new(); + // TODO: Allow fully custom properties for nodes (should only be done for proto nodes where the number of inputs is known) let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); for input_index in 0..number_of_inputs { let row = context.call_widget_override(&node_id, input_index).unwrap_or_else(|| { diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 53fa00834d..2f384fb6fa 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -54,11 +54,11 @@ async fn assign_colors( vector_group: impl Node, #[default(true)] fill: bool, stroke: bool, - gradient: GradientStops, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] gradient: GradientStops, reverse: bool, - randomize: bool, - seed: SeedValue, - repeat_every: u32, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_randomize")] randomize: bool, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] seed: SeedValue, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every_row")] repeat_every: u32, ) -> T { let mut input = vector_group.eval(footprint).await; let length = input.vector_iter_mut().count(); From 885843fab375b955d8caaff9b7e9b6bdbf005ce4 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 17 Dec 2024 23:26:58 -0800 Subject: [PATCH 13/19] WIP: Full node property override --- .../node_graph/document_node_definitions.rs | 61 +++++++++++++++++++ .../node_graph/node_graph_message_handler.rs | 2 +- .../document/node_graph/node_properties.rs | 32 ++++++---- .../utility_types/network_interface.rs | 13 ++-- node-graph/gcore/src/raster/adjustments.rs | 2 +- node-graph/gcore/src/registry.rs | 1 + node-graph/node-macro/src/codegen.rs | 3 + node-graph/node-macro/src/parsing.rs | 20 ++++++ 8 files changed, 115 insertions(+), 19 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 732ad66e33..5f4414c0e6 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 @@ -74,6 +74,11 @@ pub struct DocumentNodeDefinition { /// User-facing description of the node's functionality. pub description: Cow<'static, str>, + + /// Node level overrides are stored based on the reference, not the instance. If the node is modified such that it becomes a local version + /// (for example an input is added), the reference is no longer to the definition, and the overrides are lost. + /// Most nodes should not use node based properties, since they are less flexible than input level properties. + pub properties: Option<&'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync)>, } // We use the once cell for lazy initialization to avoid the overhead of reconstructing the node list every time. @@ -99,6 +104,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("A default node network you can use to create your own custom nodes."), + properties: None, }, // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { @@ -121,6 +127,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("The identity node passes its data through. You can use this to organize your node graph."), + properties: None, }, // TODO: Auto-generate this from its proto node macro DocumentNodeDefinition { @@ -144,6 +151,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."), + properties: None, }, DocumentNodeDefinition { identifier: "Merge", @@ -250,6 +258,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("The Merge node combines graphical data through composition."), + properties: None, }, DocumentNodeDefinition { identifier: "Artboard", @@ -360,6 +369,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."), + properties: None, }, DocumentNodeDefinition { identifier: "Load Image", @@ -440,6 +450,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Loads an image from a given url."), + properties: None, }, DocumentNodeDefinition { identifier: "Create Canvas", @@ -504,6 +515,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates a new canvas object."), + properties: None, }, DocumentNodeDefinition { identifier: "Draw Canvas", @@ -596,6 +608,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Draws raster data to a canvas element."), + properties: None, }, DocumentNodeDefinition { identifier: "Rasterize", @@ -687,6 +700,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Rasterizes the given vector data"), + properties: None, }, DocumentNodeDefinition { identifier: "Image Frame", @@ -758,6 +772,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Creates an embedded image with the given transform."), + properties: None, }, DocumentNodeDefinition { identifier: "Noise Pattern", @@ -836,6 +851,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("Generates different noise patterns."), + properties: None, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. // TODO: Auto-generate this from its proto node macro @@ -858,6 +874,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. // TODO: Auto-generate this from its proto node macro @@ -885,6 +902,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. DocumentNodeDefinition { @@ -909,6 +927,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Split Channels", @@ -1022,6 +1041,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Brush", @@ -1090,6 +1110,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Memoize", @@ -1108,6 +1129,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Memoize Impure", @@ -1126,6 +1148,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Image", @@ -1174,6 +1197,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1254,6 +1278,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Storage", @@ -1332,6 +1357,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Create Output Buffer", @@ -1410,6 +1436,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1498,6 +1525,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1522,6 +1550,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1601,6 +1630,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1680,6 +1710,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1745,6 +1776,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1815,6 +1847,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1895,6 +1928,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, #[cfg(feature = "gpu")] DocumentNodeDefinition { @@ -1916,6 +1950,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Extract", @@ -1933,6 +1968,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { // Aims for interoperable compatibility with: @@ -1963,6 +1999,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure @@ -1986,6 +2023,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, (*IMAGINATE_NODE).clone(), DocumentNodeDefinition { @@ -2009,6 +2047,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Spline", @@ -2031,6 +2070,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Path", @@ -2101,6 +2141,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Text", @@ -2129,6 +2170,7 @@ fn static_nodes() -> Vec { }, }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Transform", @@ -2209,6 +2251,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Boolean Operation", @@ -2279,6 +2322,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Copy to Points", @@ -2317,6 +2361,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Sample Points", @@ -2408,6 +2453,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, DocumentNodeDefinition { identifier: "Scatter Points", @@ -2481,6 +2527,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, // TODO: This needs to work with resolution-aware (raster with footprint, post-Cull node) data. DocumentNodeDefinition { @@ -2500,6 +2547,7 @@ fn static_nodes() -> Vec { }, description: Cow::Borrowed("TODO"), + properties: None, }, ]; @@ -2538,6 +2586,7 @@ fn static_nodes() -> Vec { category, fields, description, + properties, } = metadata; let Some(implementations) = &node_registry.get(&id) else { continue }; let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect(); @@ -2598,6 +2647,7 @@ fn static_nodes() -> Vec { }, category: category.unwrap_or("UNCATEGORIZED"), description: Cow::Borrowed(description), + properties: properties.map(|properties| name_to_properties(properties)), }; custom.push(node); } @@ -2724,8 +2774,19 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN }, description: Cow::Borrowed("TODO"), + properties: None, }); +fn name_to_properties(properties_string: &str) -> &'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync) { + match properties_string { + "channel_mixer_properties" => &node_properties::channel_mixer_properties, + _ => { + log::error!("Node properties not found for {properties_string}"); + &node_properties::node_no_properties + } + } +} + static INPUT_OVERRIDES: once_cell::sync::Lazy Result, String> + Send + Sync>>> = once_cell::sync::Lazy::new(static_input_properties); 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 107bc0eb4d..c493d57901 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 @@ -2292,7 +2292,7 @@ impl NodeGraphMessageHandler { .node_metadata(&node_id, breadcrumb_network_path) .is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()), can_be_layer: can_be_layer_lookup.contains(&node_id), - reference: network_interface.reference(&node_id, breadcrumb_network_path), + reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned(), display_name: network_interface.frontend_display_name(&node_id, breadcrumb_network_path), primary_input, exposed_inputs, 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 14751ee676..6f06a010bb 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1,6 +1,6 @@ #![allow(clippy::too_many_arguments)] -use super::document_node_definitions::{NodePropertiesContext, IMAGINATE_NODE}; +use super::document_node_definitions::{DocumentNodeDefinition, NodePropertiesContext, IMAGINATE_NODE}; use super::utility_types::FrontendGraphDataType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::network_interface::InputConnector; @@ -1188,7 +1188,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo ) } -pub(crate) fn channel_mixer_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn channel_mixer_properties(node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { + // TODO: Get node + let document_node = &DocumentNode::default(); // Monochrome let monochrome_index = 1; let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", CheckboxInput::default(), true); @@ -2094,19 +2096,27 @@ pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _c pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup { let mut layout = Vec::new(); - // TODO: Allow fully custom properties for nodes (should only be done for proto nodes where the number of inputs is known) - let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); - for input_index in 0..number_of_inputs { - let row = context.call_widget_override(&node_id, input_index).unwrap_or_else(|| { - let input_type = context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path); - property_from_type(node_id, input_index, &input_type.0, context) - }); - layout.extend(row); + if let Some(properties_override) = context + .network_interface + .reference(&node_id, context.selection_network_path) + .and_then(|reference| super::document_node_definitions::resolve_document_node_type(&reference)) + .and_then(|definition| definition.properties) + { + layout = properties_override(node_id, context); + } else { + let number_of_inputs = context.network_interface.number_of_inputs(&node_id, context.selection_network_path); + for input_index in 0..number_of_inputs { + let row = context.call_widget_override(&node_id, input_index).unwrap_or_else(|| { + let input_type = context.network_interface.input_type(&InputConnector::node(node_id, input_index), context.selection_network_path); + property_from_type(node_id, input_index, &input_type.0, context) + }); + layout.extend(row); + } } if layout.is_empty() { layout = node_no_properties(node_id, context); } - let name = context.network_interface.reference(&node_id, context.selection_network_path).clone().unwrap_or_default(); + let name = context.network_interface.reference(&node_id, context.selection_network_path).cloned().unwrap_or_default(); let visible = context.network_interface.is_visible(&node_id, context.selection_network_path); let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path); LayoutGroup::Section { 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 e3ae09156e..9ec0fa629a 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1040,9 +1040,8 @@ impl NodeNetworkInterface { } } - pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option { - self.node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.reference.as_ref().map(|reference| reference.to_string())) + pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&String> { + self.node_metadata(node_id, network_path).and_then(|node_metadata| node_metadata.persistent_metadata.reference.as_ref()) } pub fn input_name(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&str> { @@ -1082,12 +1081,12 @@ impl NodeNetworkInterface { .persistent_metadata .is_layer(); let reference = self.reference(node_id, network_path); - let is_merge_node = reference.as_ref().is_some_and(|reference| reference == "Merge"); + let is_merge_node = reference.as_ref().is_some_and(|reference| *reference == "Merge"); if self.display_name(node_id, network_path).is_empty() { if is_layer && is_merge_node { "Untitled Layer".to_string() } else { - reference.unwrap_or("Untitled node".to_string()) + reference.cloned().unwrap_or("Untitled node".to_string()) } } else { self.display_name(node_id, network_path) @@ -1174,7 +1173,7 @@ impl NodeNetworkInterface { pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool { self.reference(node_id, network_path) .as_ref() - .is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[])) + .is_some_and(|reference| *reference == "Artboard" && self.connected_to_output(node_id, &[])) } pub fn all_artboards(&self) -> HashSet { @@ -6036,6 +6035,8 @@ impl PropertiesRow { pub struct DocumentNodePersistentMetadata { /// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties if no display name is set. /// Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition. + /// TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs. + /// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition. pub reference: Option, /// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics. #[serde(default)] diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 5440b0d69a..16373ed1e2 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1185,7 +1185,7 @@ impl DomainWarpType { // Aims for interoperable compatibility with: // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=%27mixr%27%20%3D%20Channel%20Mixer // https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Lab%20color%20only-,Channel%20Mixer,-Key%20is%20%27mixr -#[node_macro::node(category("Raster: Adjustment"))] +#[node_macro::node(category("Raster: Adjustment"), properties("channel_mixer_properties"))] async fn channel_mixer>( #[implementations( (), diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 897bb143d6..a42f687bf8 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -36,6 +36,7 @@ pub struct NodeMetadata { pub category: Option<&'static str>, pub fields: Vec, pub description: &'static str, + pub properties: Option<&'static str>, } // Translation struct between macro and definition diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index 7832aa423e..95f71741a6 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -221,6 +221,8 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result syn::Result, pub(crate) path: Option, pub(crate) skip_impl: bool, + pub(crate) properties_string: Option, // Add more attributes as needed } @@ -168,6 +169,7 @@ impl Parse for NodeFnAttributes { let mut display_name = None; let mut path = None; let mut skip_impl = false; + let mut properties_string = None; let content = input; // let content; @@ -207,6 +209,16 @@ impl Parse for NodeFnAttributes { } skip_impl = true; } + Meta::List(meta) if meta.path.is_ident("properties") => { + if properties_string.is_some() { + return Err(Error::new_spanned(path, "Multiple 'properties_string' attributes are not allowed")); + } + let parsed_properties_string: LitStr = meta + .parse_args() + .map_err(|_| Error::new_spanned(meta, "Expected a string for 'properties', e.g., name(\"channel_mixer_properties\")"))?; + + properties_string = Some(parsed_properties_string); + } _ => { return Err(Error::new_spanned( meta, @@ -229,6 +241,7 @@ impl Parse for NodeFnAttributes { display_name, path, skip_impl, + properties_string, }) } } @@ -637,6 +650,7 @@ mod tests { display_name: None, path: Some(parse_quote!(graphene_core::TestNode)), skip_impl: true, + properties_string: None, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -689,6 +703,7 @@ mod tests { display_name: None, path: None, skip_impl: false, + properties_string: None, }, fn_name: Ident::new("transform", Span::call_site()), struct_name: Ident::new("Transform", Span::call_site()), @@ -747,6 +762,7 @@ mod tests { display_name: None, path: None, skip_impl: false, + properties_string: None, }, fn_name: Ident::new("circle", Span::call_site()), struct_name: Ident::new("Circle", Span::call_site()), @@ -795,6 +811,7 @@ mod tests { display_name: None, path: None, skip_impl: false, + properties_string: None, }, fn_name: Ident::new("levels", Span::call_site()), struct_name: Ident::new("Levels", Span::call_site()), @@ -854,6 +871,7 @@ mod tests { display_name: None, path: Some(parse_quote!(graphene_core::TestNode)), skip_impl: false, + properties_string: None, }, fn_name: Ident::new("add", Span::call_site()), struct_name: Ident::new("Add", Span::call_site()), @@ -902,6 +920,7 @@ mod tests { display_name: None, path: None, skip_impl: false, + properties_string: None, }, fn_name: Ident::new("load_image", Span::call_site()), struct_name: Ident::new("LoadImage", Span::call_site()), @@ -950,6 +969,7 @@ mod tests { display_name: Some(parse_quote!("CustomNode2")), path: None, skip_impl: false, + properties_string: None, }, fn_name: Ident::new("custom_node", Span::call_site()), struct_name: Ident::new("CustomNode", Span::call_site()), From 04db166892d29ebca50868b35391e6de5ffc4832 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 19 Dec 2024 10:54:13 -0800 Subject: [PATCH 14/19] Node based properties override for proto nodes --- .../node_graph/document_node_definitions.rs | 6 ++ .../document/node_graph/node_properties.rs | 66 ++++++++++++++++--- .../utility_types/network_interface.rs | 10 ++- node-graph/gcore/src/raster/adjustments.rs | 2 +- .../gcore/src/vector/generator_nodes.rs | 2 +- node-graph/gcore/src/vector/vector_nodes.rs | 6 +- 6 files changed, 77 insertions(+), 15 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 5f4414c0e6..12cd5208f8 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 @@ -2780,6 +2780,12 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN fn name_to_properties(properties_string: &str) -> &'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync) { match properties_string { "channel_mixer_properties" => &node_properties::channel_mixer_properties, + "fill_properties" => &node_properties::fill_properties, + "stroke_properties" => &node_properties::stroke_properties, + "offset_path_properties" => &node_properties::offset_path_properties, + "selective_color_properties" => &node_properties::selective_color_properties, + "exposure_properties" => &node_properties::exposure_properties, + "rectangle_properties" => &node_properties::rectangle_properties, _ => { log::error!("Node properties not found for {properties_string}"); &node_properties::node_no_properties 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 6f06a010bb..d0f6d85ab1 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1188,9 +1188,15 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo ) } -pub(crate) fn channel_mixer_properties(node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - // TODO: Get node - let document_node = &DocumentNode::default(); +pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in channel_mixer_properties: {err}"); + return Vec::new(); + } + }; + // Monochrome let monochrome_index = 1; let monochrome = bool_widget(document_node, node_id, monochrome_index, "Monochrome", CheckboxInput::default(), true); @@ -1290,7 +1296,14 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, _context: &mut NodePrope layout } -pub(crate) fn selective_color_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in selective_color_properties: {err}"); + return Vec::new(); + } + }; // Colors choice let colors_index = 38; let mut colors = vec![TextLabel::new("Colors").widget_holder(), Separator::new(SeparatorType::Unrelated).widget_holder()]; @@ -1386,7 +1399,14 @@ pub(crate) fn _gpu_map_properties(document_node: &DocumentNode, node_id: NodeId, vec![LayoutGroup::Row { widgets: map }] } -pub(crate) fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in exposure_properties: {err}"); + return Vec::new(); + } + }; let exposure = number_widget(document_node, node_id, 1, "Exposure", NumberInput::default().min(-20.).max(20.), true); let offset = number_widget(document_node, node_id, 2, "Offset", NumberInput::default().min(-0.5).max(0.5), true); let gamma_input = NumberInput::default().min(0.01).max(9.99).increment_step(0.1); @@ -1399,7 +1419,14 @@ pub(crate) fn exposure_properties(document_node: &DocumentNode, node_id: NodeId, ] } -pub(crate) fn rectangle_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in rectangle_properties: {err}"); + return Vec::new(); + } + }; let size_x_index = 1; let size_y_index = 2; let corner_rounding_type_index = 3; @@ -2227,7 +2254,14 @@ pub(crate) fn poisson_disk_points_properties(document_node: &DocumentNode, node_ } /// Fill Node Widgets LayoutGroup -pub(crate) fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in fill_properties: {err}"); + return Vec::new(); + } + }; let fill_index = 1; let backup_color_index = 2; let backup_gradient_index = 3; @@ -2402,7 +2436,14 @@ pub(crate) fn fill_properties(document_node: &DocumentNode, node_id: NodeId, _co widgets } -pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in fill_properties: {err}"); + return Vec::new(); + } + }; let color_index = 1; let weight_index = 2; let dash_lengths_index = 3; @@ -2441,7 +2482,14 @@ pub fn stroke_properties(document_node: &DocumentNode, node_id: NodeId, _context ] } -pub fn offset_path_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { +pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in offset_path_properties: {err}"); + return Vec::new(); + } + }; let distance_index = 1; let line_join_index = 2; let miter_limit_index = 3; 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 9ec0fa629a..cae5e12f16 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -3280,6 +3280,8 @@ impl NodeNetworkInterface { } else { encapsulating_node_metadata.persistent_metadata.output_names.insert(insert_index as usize, output_name.to_string()); } + // Clear the reference to the nodes definition + encapsulating_node_metadata.persistent_metadata.reference = None; }; // Update the export ports and outward wires for the current network @@ -3345,6 +3347,9 @@ impl NodeNetworkInterface { node_metadata.persistent_metadata.input_properties.insert(insert_index as usize, input_name.into()); } + // Clear the reference to the nodes definition + node_metadata.persistent_metadata.reference = None; + // Update the metadata for the encapsulating node self.unload_node_click_targets(&node_id, &encapsulating_network_path); self.unload_all_nodes_bounding_box(&encapsulating_network_path); @@ -3399,6 +3404,7 @@ impl NodeNetworkInterface { return; }; encapsulating_node_metadata.persistent_metadata.output_names.remove(export_index); + encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the encapsulating node self.unload_outward_wires(&encapsulating_network_path); @@ -3473,6 +3479,7 @@ impl NodeNetworkInterface { return; }; encapsulating_node_metadata.persistent_metadata.input_properties.remove(import_index); + encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the encapsulating node self.unload_outward_wires(&encapsulating_network_path); @@ -3518,6 +3525,7 @@ impl NodeNetworkInterface { let name = encapsulating_node_metadata.persistent_metadata.output_names.remove(start_index); encapsulating_node_metadata.persistent_metadata.output_names.insert(end_index, name); + encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the encapsulating network self.unload_outward_wires(&encapsulating_network_path); @@ -3609,6 +3617,7 @@ impl NodeNetworkInterface { let properties_row = encapsulating_node_metadata.persistent_metadata.input_properties.remove(start_index); encapsulating_node_metadata.persistent_metadata.input_properties.insert(end_index, properties_row); + encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the outer network self.unload_outward_wires(&encapsulating_network_path); @@ -6044,7 +6053,6 @@ pub struct DocumentNodePersistentMetadata { /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. /// Must match the length of node inputs pub input_properties: Vec, - /// A node can have a fully custom properties panel. For example to display a single string, or if interdependent properties are needed pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. /// True for most nodes, but the Split Channels node is an example of a node that has multiple secondary outputs but no primary output. diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index 16373ed1e2..e454e05773 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1555,7 +1555,7 @@ async fn posterize>( // // Algorithm based on: // https://geraldbakker.nl/psnumbers/exposure.html -#[node_macro::node(category("Raster: Adjustment"))] +#[node_macro::node(category("Raster: Adjustment"), properties("exposure_properties"))] async fn exposure>( #[implementations( (), diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index 88848f90f1..53292d8a08 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -55,7 +55,7 @@ fn ellipse(#[implementations((), Footprint)] _footprint: F, _prima ellipse } -#[node_macro::node(category("Vector: Shape"))] +#[node_macro::node(category("Vector: Shape"), properties("rectangle_properties"))] fn rectangle( #[implementations((), Footprint)] _footprint: F, _primary: (), diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 2f384fb6fa..939ab89159 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -90,7 +90,7 @@ async fn assign_colors( input } -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] async fn fill + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( #[implementations( (), @@ -162,7 +162,7 @@ async fn fill + 'n + Send, TargetTy: VectorIter target } -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] async fn stroke> + 'n + Send, TargetTy: VectorIterMut + 'n + Send>( #[implementations( (), @@ -431,7 +431,7 @@ async fn bounding_box( result } -#[node_macro::node(category("Vector"), path(graphene_core::vector))] +#[node_macro::node(category("Vector"), path(graphene_core::vector), properties("offset_path_properties"))] async fn offset_path( #[implementations( (), From e1d435e0625c226734657aee11c911b1e3199f0c Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 22 Dec 2024 23:03:56 -0800 Subject: [PATCH 15/19] Migrate all node properties to be input based --- .../node_graph/document_node_definitions.rs | 447 +++++++++++++++++- .../document/node_graph/node_properties.rs | 205 +------- .../utility_types/network_interface.rs | 100 +++- node-graph/gcore/src/raster/adjustments.rs | 8 +- node-graph/node-macro/src/parsing.rs | 7 +- 5 files changed, 532 insertions(+), 235 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 12cd5208f8..bbe50cdffb 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 @@ -2,8 +2,8 @@ use super::node_properties; use super::utility_types::FrontendNodeType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::network_interface::{ - DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, PropertiesRow, - WidgetOverride, + DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings, + PropertiesRow, Vec2InputSettings, WidgetOverride, }; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::Message; @@ -50,6 +50,17 @@ impl NodePropertiesContext<'_> { return None; }; widget_override_lambda(*node_id, index, self) + .map(|layout_group| { + let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { + log::error!("Could not get input properties row in call_widget_override"); + return Vec::new(); + }; + if let Some(tooltip) = &input_properties_row.tooltip { + layout_group.into_iter().map(|widget| widget.with_tooltip(tooltip)).collect::>() + } else { + layout_group + } + }) .map_err(|error| { log::error!("Error in widget override lambda: {}", error); }) @@ -322,9 +333,25 @@ fn static_nodes() -> Vec { input_properties: vec![ PropertiesRow::with_override("Artboards", WidgetOverride::Hidden), PropertiesRow::with_override("Contents", WidgetOverride::Hidden), - "Location".into(), - "Dimensions".into(), - "Background".into(), + PropertiesRow::with_override( + "Location", + WidgetOverride::Vec2(Vec2InputSettings { + x: "X".to_string(), + y: "Y".to_string(), + unit: " px".to_string(), + ..Default::default() + }), + ), + PropertiesRow::with_override( + "Dimensions", + WidgetOverride::Vec2(Vec2InputSettings { + x: "W".to_string(), + y: "H".to_string(), + unit: " px".to_string(), + ..Default::default() + }), + ), + PropertiesRow::with_override("Background", WidgetOverride::Custom("artboard_background".to_string())), "Clip".into(), ], output_names: vec!["Out".to_string()], @@ -408,7 +435,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![PropertiesRow::with_override("api", WidgetOverride::Hidden), "path".into()], + input_properties: vec![PropertiesRow::with_override("api", WidgetOverride::Hidden), "URL".into()], output_names: vec!["Image Frame".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -658,7 +685,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Artwork".into(), "Footprint".into()], + input_properties: vec![PropertiesRow::with_override("Artwork", WidgetOverride::Hidden), "Footprint".into()], output_names: vec!["Canvas".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -868,7 +895,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![PropertiesRow::with_override("Image", WidgetOverride::Hidden), "Stencil".into()], + input_properties: vec![ + PropertiesRow::with_override("Image", WidgetOverride::Hidden), + PropertiesRow::with_override("Stencil", WidgetOverride::Custom("mask_stencil".to_string())), + ], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -895,7 +925,7 @@ fn static_nodes() -> Vec { input_properties: vec![ PropertiesRow::with_override("Image", WidgetOverride::Hidden), PropertiesRow::with_override("Insertion", WidgetOverride::Hidden), - "Replace".into(), + "Into".into(), ], output_names: vec!["Image".to_string()], ..Default::default() @@ -2041,7 +2071,27 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["None".into(), "Start".into(), "End".into()], + input_properties: vec![ + PropertiesRow::with_override("None", WidgetOverride::Hidden), + PropertiesRow::with_override( + "Start", + WidgetOverride::Vec2(Vec2InputSettings { + x: "X".to_string(), + y: "Y".to_string(), + unit: " px".to_string(), + ..Default::default() + }), + ), + PropertiesRow::with_override( + "End", + WidgetOverride::Vec2(Vec2InputSettings { + x: "X".to_string(), + y: "Y".to_string(), + unit: " px".to_string(), + ..Default::default() + }), + ), + ], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2064,7 +2114,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["None".into(), "Points".into()], + input_properties: vec![ + PropertiesRow::with_override("None", WidgetOverride::Hidden), + PropertiesRow::with_override("Points", WidgetOverride::Custom("spline_input".to_string())), + ], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2164,7 +2217,35 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Editor API".into(), "Text".into(), "Font".into(), "Size".into(), "Line Height".into(), "Character Spacing".into()], + input_properties: vec![ + PropertiesRow::with_override("Editor API", WidgetOverride::Hidden), + PropertiesRow::with_override("Text", WidgetOverride::Custom("text_area".to_string())), + PropertiesRow::with_override("Font", WidgetOverride::Custom("text_font".to_string())), + PropertiesRow::with_override( + "Size", + WidgetOverride::Number(NumberInputSettings { + unit: Some(" px".to_string()), + min: Some(1.), + ..Default::default() + }), + ), + PropertiesRow::with_override( + "Line Height", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + step: Some(0.1), + ..Default::default() + }), + ), + PropertiesRow::with_override( + "Character Spacing", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + step: Some(0.1), + ..Default::default() + }), + ), + ], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2244,7 +2325,30 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec!["Vector Data".into(), "Translation".into(), "Rotation".into(), "Scale".into(), "Skew".into(), "Pivot".into()], + input_properties: vec![ + PropertiesRow::with_override("Vector Data", WidgetOverride::Hidden), + PropertiesRow::with_override( + "Translation", + WidgetOverride::Vec2(Vec2InputSettings { + x: "X".to_string(), + y: "Y".to_string(), + unit: " px".to_string(), + ..Default::default() + }), + ), + PropertiesRow::with_override("Rotation", WidgetOverride::Custom("transform_rotation".to_string())), + PropertiesRow::with_override( + "Scale", + WidgetOverride::Vec2(Vec2InputSettings { + x: "W".to_string(), + y: "H".to_string(), + unit: "x".to_string(), + ..Default::default() + }), + ), + PropertiesRow::with_override("Skew", WidgetOverride::Hidden), + PropertiesRow::with_override("Pivot", WidgetOverride::Hidden), + ], output_names: vec!["Data".to_string()], ..Default::default() }, @@ -2315,7 +2419,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec!["Group of Paths".into(), "Operation".into()], + input_properties: vec![PropertiesRow::with_override("Group of Paths", WidgetOverride::Hidden), "Operation".into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2346,14 +2450,72 @@ fn static_nodes() -> Vec { }, persistent_node_metadata: DocumentNodePersistentMetadata { input_properties: vec![ - "Points".into(), - "Instance".into(), - "Random Scale Min".into(), - "Random Scale Max".into(), - "Random Scale Bias".into(), - "Random Scale Seed".into(), - "Random Rotation".into(), - "Random Rotation Seed".into(), + PropertiesRow::with_override("Points", WidgetOverride::Hidden), + Into::::into("Instance").with_tooltip("Artwork to be copied and placed at each point"), + PropertiesRow::with_override( + "Random Scale Min", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + mode: NumberInputMode::Range, + range_min: Some(0.), + range_max: Some(2.), + unit: Some("x".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Minimum range of randomized sizes given to each instance"), + PropertiesRow::with_override( + "Random Scale Max", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + mode: NumberInputMode::Range, + range_min: Some(0.), + range_max: Some(2.), + unit: Some("x".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Minimum range of randomized sizes given to each instance"), + PropertiesRow::with_override( + "Random Scale Bias", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + mode: NumberInputMode::Range, + range_min: Some(-50.), + range_max: Some(50.), + ..Default::default() + }), + ) + .with_tooltip("Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes)"), + PropertiesRow::with_override( + "Random Scale Seed", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + is_integer: true, + ..Default::default() + }), + ) + .with_tooltip("Seed to determine unique variations on all the randomized instance sizes"), + PropertiesRow::with_override( + "Random Rotation", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + max: Some(360.), + mode: NumberInputMode::Range, + unit: Some("°".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise"), + PropertiesRow::with_override( + "Random Rotation Seed", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + is_integer: true, + ..Default::default() + }), + ) + .with_tooltip("Seed to determine unique variations on all the randomized instance angles"), ], output_names: vec!["Vector".to_string()], ..Default::default() @@ -2446,7 +2608,37 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec!["Vector Data".into(), "Spacing".into(), "Start Offset".into(), "Stop Offset".into(), "Adaptive Spacing".into()], + input_properties: vec![ + PropertiesRow::with_override("Vector Data", WidgetOverride::Hidden), + PropertiesRow::with_override( + "Spacing", + WidgetOverride::Number(NumberInputSettings { + min: Some(1.), + unit: Some(" px".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled)"), + PropertiesRow::with_override( + "Start Offset", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + unit: Some(" px".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Exclude some distance from the start of the path before the first instance"), + PropertiesRow::with_override( + "Stop Offset", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + unit: Some(" px".to_string()), + ..Default::default() + }), + ) + .with_tooltip("Exclude some distance from the end of the path after the last instance"), + Into::::into("Adaptive Spacing").with_tooltip("Round 'Spacing' to a nearby value that divides into the path length evenly"), + ], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2520,7 +2712,27 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec!["Vector Data".into(), "Separation Disk Diameter".into(), "Seed".into()], + input_properties: vec![ + PropertiesRow::with_override("Vector Data", WidgetOverride::Hidden), + PropertiesRow::with_override( + "Separation Disk Diameter", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.01), + mode: NumberInputMode::Range, + range_min: Some(1.), + range_max: Some(100.), + ..Default::default() + }), + ), + PropertiesRow::with_override( + "Seed", + WidgetOverride::Number(NumberInputSettings { + min: Some(0.), + is_integer: true, + ..Default::default() + }), + ), + ], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -2540,7 +2752,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec!["Segmentation".into(), "Index".into()], + input_properties: vec![ + PropertiesRow::with_override("Segmentation", WidgetOverride::Hidden), + PropertiesRow::with_override("Index", WidgetOverride::Number(NumberInputSettings { min: Some(0.), ..Default::default() })), + ], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -2812,6 +3027,104 @@ fn static_input_properties() -> HashMap HashMap HashMap(value: impl Fn(&T) -> Option + 'stati } } -fn update_value(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { +pub fn update_value(value: impl Fn(&T) -> TaggedValue + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync { optionally_update_value(move |v| Some(value(v)), node_id, input_index) } -fn commit_value(_: &T) -> Message { +pub fn commit_value(_: &T) -> Message { DocumentMessage::AddTransaction.into() } @@ -1023,7 +1023,6 @@ pub fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, }; widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - match &**tagged_value { TaggedValue::Color(color) => widgets.push( color_button @@ -1111,24 +1110,6 @@ pub fn centroid_widget(document_node: &DocumentNode, node_id: NodeId, index: usi LayoutGroup::Row { widgets } } -pub(crate) fn load_image_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let url = text_widget(document_node, node_id, 1, "URL", true); - - vec![LayoutGroup::Row { widgets: url }] -} - -pub(crate) fn mask_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let mask = color_widget(document_node, node_id, 1, "Stencil", ColorButton::default(), true); - - vec![mask] -} - -pub(crate) fn insert_channel_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let color_channel = color_channel(document_node, node_id, 2, "Into", true); - - vec![color_channel] -} - pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> { let network = context.network_interface.network(context.selection_network_path).ok_or("network not found in get_document_node")?; network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node")) @@ -1551,71 +1532,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties ] } -pub(crate) fn line_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let operand = |name: &str, index| vec2_widget(document_node, node_id, index, name, "X", "Y", " px", None, add_blank_assist); - vec![operand("Start", 1), operand("End", 2)] -} - -pub(crate) fn spline_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - vec![LayoutGroup::Row { - widgets: vec_dvec2_input(document_node, node_id, 1, "Points", TextInput::default().centered(true), true), - }] -} - -pub(crate) fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", " px", None, add_blank_assist); - - let rotation = { - let index = 2; - - let mut widgets = start_widgets(document_node, node_id, index, "Rotation", FrontendGraphDataType::Number, true); - - let Some(input) = document_node.inputs.get(index) else { - log::warn!("A widget failed to be built because its node's input index is invalid."); - return vec![]; - }; - if let Some(&TaggedValue::F64(val)) = input.as_non_exposed_value() { - widgets.extend_from_slice(&[ - Separator::new(SeparatorType::Unrelated).widget_holder(), - NumberInput::new(Some(val.to_degrees())) - .unit("°") - .mode(NumberInputMode::Range) - .range_min(Some(-180.)) - .range_max(Some(180.)) - .on_update(update_value(|number_input: &NumberInput| TaggedValue::F64(number_input.value.unwrap().to_radians()), node_id, index)) - .on_commit(commit_value) - .widget_holder(), - ]); - } - - LayoutGroup::Row { widgets } - }; - - let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", None, add_blank_assist); - - vec![translation, rotation, scale] -} -pub(crate) fn rasterize_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - footprint_widget(document_node, node_id, 1) -} - -pub(crate) fn text_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let text = text_area_widget(document_node, node_id, 1, "Text", true); - let (font, style) = font_inputs(document_node, node_id, 2, "Font", true); - let size = number_widget(document_node, node_id, 3, "Size", NumberInput::default().unit(" px").min(1.), true); - let line_height_ratio = number_widget(document_node, node_id, 4, "Line Height", NumberInput::default().min(0.).step(0.1), true); - let character_spacing = number_widget(document_node, node_id, 5, "Character Spacing", NumberInput::default().min(0.).step(0.1), true); - - let mut result = vec![LayoutGroup::Row { widgets: text }, LayoutGroup::Row { widgets: font }]; - if let Some(style) = style { - result.push(LayoutGroup::Row { widgets: style }); - } - result.push(LayoutGroup::Row { widgets: size }); - result.push(LayoutGroup::Row { widgets: line_height_ratio }); - result.push(LayoutGroup::Row { widgets: character_spacing }); - result -} - pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { let imaginate_node = [context.selection_network_path, &[node_id]].concat(); @@ -2114,12 +2030,6 @@ pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesCo }) } -pub(crate) fn index_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let index = number_widget(document_node, node_id, 1, "Index", NumberInput::default().min(0.), true); - - vec![LayoutGroup::Row { widgets: index }] -} - pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> LayoutGroup { let mut layout = Vec::new(); @@ -2155,104 +2065,6 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper } } -pub(crate) fn boolean_operation_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let operation_index = 1; - let operation = boolean_operation_radio_buttons(document_node, node_id, operation_index, "Operation", true); - - vec![operation] -} - -pub(crate) fn copy_to_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let instance_index = 1; - let random_scale_min_index = 2; - let random_scale_max_index = 3; - let random_scale_bias_index = 4; - let random_scale_seed_index = 5; - let random_rotation_index = 6; - let random_rotation_seed_index = 7; - - let instance = vector_widget(document_node, node_id, instance_index, "Instance", true); - - let random_scale_min = number_widget( - document_node, - node_id, - random_scale_min_index, - "Random Scale Min", - NumberInput::default().min(0.).mode_range().range_min(Some(0.)).range_max(Some(2.)).unit("x"), - true, - ); - let random_scale_max = number_widget( - document_node, - node_id, - random_scale_max_index, - "Random Scale Max", - NumberInput::default().min(0.).mode_range().range_min(Some(0.)).range_max(Some(2.)).unit("x"), - true, - ); - let random_scale_bias = number_widget( - document_node, - node_id, - random_scale_bias_index, - "Random Scale Bias", - NumberInput::default().mode_range().range_min(Some(-50.)).range_max(Some(50.)), - true, - ); - let random_scale_seed = number_widget(document_node, node_id, random_scale_seed_index, "Random Scale Seed", NumberInput::default().int().min(0.), true); - - let random_rotation = number_widget( - document_node, - node_id, - random_rotation_index, - "Random Rotation", - NumberInput::default().min(0.).max(360.).mode_range().unit("°"), - true, - ); - let random_rotation_seed = number_widget(document_node, node_id, random_rotation_seed_index, "Random Rotation Seed", NumberInput::default().int().min(0.), true); - - vec![ - LayoutGroup::Row { widgets: instance }.with_tooltip("Artwork to be copied and placed at each point"), - LayoutGroup::Row { widgets: random_scale_min }.with_tooltip("Minimum range of randomized sizes given to each instance"), - LayoutGroup::Row { widgets: random_scale_max }.with_tooltip("Maximum range of randomized sizes given to each instance"), - LayoutGroup::Row { widgets: random_scale_bias } - .with_tooltip("Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes)"), - LayoutGroup::Row { widgets: random_scale_seed }.with_tooltip("Seed to determine unique variations on all the randomized instance sizes"), - LayoutGroup::Row { widgets: random_rotation }.with_tooltip("Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise"), - LayoutGroup::Row { widgets: random_rotation_seed }.with_tooltip("Seed to determine unique variations on all the randomized instance angles"), - ] -} - -pub(crate) fn sample_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let spacing = number_widget(document_node, node_id, 1, "Spacing", NumberInput::default().min(1.).unit(" px"), true); - let start_offset = number_widget(document_node, node_id, 2, "Start Offset", NumberInput::default().min(0.).unit(" px"), true); - let stop_offset = number_widget(document_node, node_id, 3, "Stop Offset", NumberInput::default().min(0.).unit(" px"), true); - let adaptive_spacing = bool_widget(document_node, node_id, 4, "Adaptive Spacing", CheckboxInput::default(), true); - - vec![ - LayoutGroup::Row { widgets: spacing }.with_tooltip("Distance between each instance (exact if 'Adaptive Spacing' is disabled, approximate if enabled)"), - LayoutGroup::Row { widgets: start_offset }.with_tooltip("Exclude some distance from the start of the path before the first instance"), - LayoutGroup::Row { widgets: stop_offset }.with_tooltip("Exclude some distance from the end of the path after the last instance"), - LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip("Round 'Spacing' to a nearby value that divides into the path length evenly"), - ] -} - -pub(crate) fn poisson_disk_points_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let separation_disk_diameter_index = 1; - let seed_index = 2; - - let spacing = number_widget( - document_node, - node_id, - separation_disk_diameter_index, - "Separation Disk Diameter", - NumberInput::default().min(0.01).mode_range().range_min(Some(1.)).range_max(Some(100.)), - true, - ); - - let seed = number_widget(document_node, node_id, seed_index, "Seed", NumberInput::default().int().min(0.), true); - - vec![LayoutGroup::Row { widgets: spacing }, LayoutGroup::Row { widgets: seed }] -} - /// Fill Node Widgets LayoutGroup pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { let document_node = match get_document_node(node_id, context) { @@ -2508,14 +2320,3 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte vec![LayoutGroup::Row { widgets: distance }, line_join, LayoutGroup::Row { widgets: miter_limit }] } - -pub(crate) fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let location = vec2_widget(document_node, node_id, 2, "Location", "X", "Y", " px", None, add_blank_assist); - let dimensions = vec2_widget(document_node, node_id, 3, "Dimensions", "W", "H", " px", None, add_blank_assist); - let background = color_widget(document_node, node_id, 4, "Background", ColorButton::default().allow_none(false), true); - let clip = bool_widget(document_node, node_id, 5, "Clip", CheckboxInput::default(), true); - - let clip_row = LayoutGroup::Row { widgets: clip }; - - vec![location, dimensions, background, clip_row] -} 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 cae5e12f16..ebe60503e4 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -6,6 +6,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Modify use crate::messages::portfolio::document::node_graph::document_node_definitions::{resolve_document_node_type, DocumentNodeDefinition}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use bezier_rs::Subpath; use graph_craft::document::{value::TaggedValue, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork}; @@ -14,7 +15,7 @@ use graphene_std::renderer::{ClickTarget, Quad}; use graphene_std::transform::Footprint; use graphene_std::vector::{PointId, VectorData, VectorModificationType}; use interpreted_executor::{dynamic_executor::ResolvedDocumentNodeTypes, node_registry::NODE_REGISTRY}; -use serde_json::Value; +use serde_json::{json, Value}; use glam::{DAffine2, DVec2, IVec2}; use std::collections::{HashMap, HashSet, VecDeque}; @@ -5972,11 +5973,50 @@ impl PartialEq for DocumentNodeMetadata { } } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct NumberInputSettings { + pub unit: Option, + pub min: Option, + pub max: Option, + pub step: Option, + pub mode: NumberInputMode, + pub range_min: Option, + pub range_max: Option, + pub is_integer: bool, + pub blank_assist: bool, +} + +impl Default for NumberInputSettings { + fn default() -> Self { + NumberInputSettings { + unit: None, + min: None, + max: None, + step: None, + mode: NumberInputMode::default(), + range_min: None, + range_max: None, + is_integer: false, + blank_assist: true, + } + } +} + +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] +pub struct Vec2InputSettings { + pub x: String, + pub y: String, + pub unit: String, + pub min: Option, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum WidgetOverride { None, Hidden, String(String), + Number(NumberInputSettings), + Vec2(Vec2InputSettings), Custom(String), } @@ -5984,10 +6024,13 @@ pub enum WidgetOverride { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct PropertiesRow { /// A general datastore than can store key value pairs of any types for any input + /// TODO: This could be simplified to just Value, and key value pairs could be stored as the Object variant pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type // The string is the identifier to the widget override function stored in INPUT_OVERRIDES pub widget_override: Option, + // Tool tip when hovering over any widgets created by the properties row + pub tooltip: Option, } impl Default for PropertiesRow { @@ -6019,24 +6062,77 @@ impl PropertiesRow { let mut input_data = HashMap::new(); input_data.insert("input_name".to_string(), Value::String(input_name.to_string())); match widget_override { - WidgetOverride::None => PropertiesRow { input_data, widget_override: None }, + WidgetOverride::None => PropertiesRow { + input_data, + widget_override: None, + tooltip: None, + }, WidgetOverride::Hidden => PropertiesRow { input_data, widget_override: Some("hidden".to_string()), + tooltip: None, }, WidgetOverride::String(string_properties) => { input_data.insert("string_properties".to_string(), Value::String(string_properties)); PropertiesRow { input_data, widget_override: Some("string".to_string()), + tooltip: None, + } + } + WidgetOverride::Number(mut number_properties) => { + if let Some(unit) = number_properties.unit.take() { + input_data.insert("unit".to_string(), json!(unit)); + } + if let Some(min) = number_properties.min.take() { + input_data.insert("min".to_string(), json!(min)); + } + if let Some(max) = number_properties.max.take() { + input_data.insert("max".to_string(), json!(max)); + } + if let Some(step) = number_properties.step.take() { + input_data.insert("step".to_string(), json!(step)); + } + if let Some(range_min) = number_properties.range_min.take() { + input_data.insert("range_min".to_string(), json!(range_min)); + } + if let Some(range_max) = number_properties.range_max.take() { + input_data.insert("range_max".to_string(), json!(range_max)); + } + input_data.insert("mode".to_string(), json!(number_properties.mode)); + input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer)); + input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist)); + PropertiesRow { + input_data, + widget_override: Some("number".to_string()), + tooltip: None, + } + } + WidgetOverride::Vec2(vec2_properties) => { + input_data.insert("x".to_string(), json!(vec2_properties.x)); + input_data.insert("y".to_string(), json!(vec2_properties.y)); + input_data.insert("unit".to_string(), json!(vec2_properties.unit)); + if let Some(min) = vec2_properties.min { + input_data.insert("min".to_string(), json!(min)); + } + PropertiesRow { + input_data, + widget_override: Some("vec2".to_string()), + tooltip: None, } } WidgetOverride::Custom(lambda_name) => PropertiesRow { input_data, widget_override: Some(lambda_name), + tooltip: None, }, } } + + pub fn with_tooltip(mut self, tooltip: &str) -> Self { + self.tooltip = Some(tooltip.to_string()); + self + } } /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. diff --git a/node-graph/gcore/src/raster/adjustments.rs b/node-graph/gcore/src/raster/adjustments.rs index e454e05773..887028a241 100644 --- a/node-graph/gcore/src/raster/adjustments.rs +++ b/node-graph/gcore/src/raster/adjustments.rs @@ -1683,7 +1683,13 @@ mod index_node { use crate::raster::{Color, ImageFrame}; #[node_macro::node(category(""))] - pub fn index(_: (), #[implementations(Vec>, Vec)] input: Vec, index: u32) -> T { + pub fn index( + _: (), + #[implementations(Vec>, Vec)] + #[widget(ParsedWidgetOverride::Hidden)] + input: Vec, + index: u32, + ) -> T { if (index as usize) < input.len() { input[index as usize].clone() } else { diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 1c89f3997a..c10a9e63d7 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -51,10 +51,9 @@ pub enum ParsedValueSource { Scope(LitStr), } -// #[widget(Hidden)] -// #[widget(String = "Some string")] -// #[widget(Custom = "Custom string")] - +// #[widget(ParsedWidgetOverride::Hidden)] +// #[widget(ParsedWidgetOverride::String = "Some string")] +// #[widget(ParsedWidgetOverride::Custom = "Custom string")] #[derive(Debug, Default)] pub enum ParsedWidgetOverride { #[default] From 3f8700df191b57f559d5ba885e79f7dfd550046b Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 25 Dec 2024 23:59:28 -0800 Subject: [PATCH 16/19] Rename imports/exports --- .../document/document_message_handler.rs | 1 + .../document/node_graph/node_graph_message.rs | 28 +++++- .../node_graph/node_graph_message_handler.rs | 7 ++ .../utility_types/network_interface.rs | 32 +++++++ frontend/src/components/views/Graph.svelte | 96 ++++++++++++++++--- frontend/wasm/src/editor_api.rs | 22 ++++- 6 files changed, 169 insertions(+), 17 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 693fe43f41..b2a1e99b52 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1019,6 +1019,7 @@ impl MessageHandler> for DocumentMessag self.graph_fade_artwork_percentage = percentage; responses.add(FrontendMessage::UpdateGraphFadeArtwork { percentage }); } + DocumentMessage::SetNodePinned { node_id, pinned } => { responses.add(DocumentMessage::StartTransaction); responses.add(NodeGraphMessage::SetPinned { node_id, pinned }); 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 7f6bde8335..4a62f8e3a7 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 @@ -1,7 +1,7 @@ use super::utility_types::Direction; use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector}; use crate::messages::prelude::*; use glam::IVec2; @@ -98,10 +98,20 @@ pub enum NodeGraphMessage { shift: Key, }, PrintSelectedNodeCoordinates, - RemoveImport { import_index: usize }, - RemoveExport { export_index: usize }, - ReorderImport { start_index: usize, end_index: usize }, - ReorderExport { start_index: usize, end_index: usize }, + RemoveImport { + import_index: usize, + }, + RemoveExport { + export_index: usize, + }, + ReorderImport { + start_index: usize, + end_index: usize, + }, + ReorderExport { + start_index: usize, + end_index: usize, + }, RunDocumentGraph, ForceRunDocumentGraph, SelectedNodesAdd { @@ -157,6 +167,14 @@ pub enum NodeGraphMessage { TogglePreviewImpl { node_id: NodeId, }, + SetImportExportName { + name: String, + index: ImportOrExport, + }, + SetImportExportNameImpl { + name: String, + index: ImportOrExport, + }, ToggleSelectedAsLayersOrNodes, ToggleSelectedLocked, ToggleLocked { 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 c493d57901..6e7b7e9252 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 @@ -1464,6 +1464,13 @@ impl<'a> MessageHandler> for NodeGrap NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => { network_interface.set_display_name(&node_id, alias, selection_network_path); } + NodeGraphMessage::SetImportExportName { name, index } => { + responses.add(DocumentMessage::StartTransaction); + responses.add(NodeGraphMessage::SetImportExportNameImpl { name, index }); + responses.add(DocumentMessage::EndTransaction); + responses.add(NodeGraphMessage::UpdateImportsExports); + } + NodeGraphMessage::SetImportExportNameImpl { name, index } => network_interface.set_import_export_name(name, index, breadcrumb_network_path), NodeGraphMessage::TogglePreview { node_id } => { responses.add(DocumentMessage::AddTransaction); responses.add(NodeGraphMessage::TogglePreviewImpl { node_id }); 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 ebe60503e4..1b1e737564 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4338,6 +4338,32 @@ impl NodeNetworkInterface { self.unload_node_click_targets(node_id, network_path); } + pub fn set_import_export_name(&mut self, name: String, index: ImportOrExport, network_path: &[NodeId]) { + let Some(encapsulating_node) = self.encapsulating_node_metadata_mut(network_path) else { + log::error!("Could not get nested network in set_import_export_name"); + return; + }; + + match index { + ImportOrExport::Import(import_index) => { + let Some(input_properties) = encapsulating_node.persistent_metadata.input_properties.get_mut(import_index) else { + log::error!("Could not get input properties in set_import_export_name"); + return; + }; + input_properties.input_data.insert("input_name".to_string(), json!(name)); + } + ImportOrExport::Export(export_index) => { + let Some(export_name) = encapsulating_node.persistent_metadata.output_names.get_mut(export_index) else { + log::error!("Could not get export_name in set_import_export_name"); + return; + }; + *export_name = name; + } + } + + self.transaction_modified(); + } + pub fn set_pinned(&mut self, node_id: &NodeId, network_path: &[NodeId], pinned: bool) { let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { log::error!("Could not get node {node_id} in set_pinned"); @@ -5598,6 +5624,12 @@ impl Default for TypeSource { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum ImportOrExport { + Import(usize), + Export(usize), +} + /// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum InputConnector { diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 12fe0da598..250d6b99cc 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -45,6 +45,35 @@ $: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths); + let editingNameImportIndex: number | undefined = undefined; + let editingNameExportIndex: number | undefined = undefined; + let editingNameText = ""; + + function setEditingImportNameIndex(index: number, currentName: string) { + editingNameText = currentName; + editingNameImportIndex = index; + } + function setEditingExportNameIndex(index: number, currentName: string) { + editingNameText = currentName; + editingNameExportIndex = index; + } + + function setEditingImportName(event: any) { + if (editingNameImportIndex !== undefined) { + let text = event.target.value; + editor.handle.setImportName(editingNameImportIndex, text); + editingNameImportIndex = undefined; + } + } + + function setEditingExportName(event: any) { + if (editingNameExportIndex !== undefined) { + let text = event.target.value; + editor.handle.setExportName(editingNameExportIndex, text); + editingNameExportIndex = undefined; + } + } + function calculateGridSpacing(scale: number): number { const dense = scale * GRID_SIZE; let sparse = dense; @@ -450,7 +479,21 @@ />
-

{outputMetadata.name}

+ +
setEditingImportNameIndex(index, outputMetadata.name)} style:--offset-left={(position.x - 24) / 24} style:--offset-top={position.y / 24}> + {#if editingNameImportIndex == index} + e.key === "Enter" && setEditingImportName(e)} + autofocus + /> + {:else} +

{outputMetadata.name}

+ {/if} +
{/each} {#if $nodeGraph.reorderImportIndex !== undefined} {@const position = { @@ -502,7 +545,20 @@ />
{/if} -

{inputMetadata.name}

+
setEditingExportNameIndex(index, inputMetadata.name)} style:--offset-left={(position.x + 24) / 24} style:--offset-top={position.y / 24}> + {#if editingNameExportIndex == index} + e.key === "Enter" && setEditingExportName(e)} + autofocus + /> + {:else} +

{inputMetadata.name}

+ {/if} +
{/each} {#if $nodeGraph.reorderExportIndex !== undefined} {@const position = { @@ -979,22 +1035,40 @@ } } - .export-text { + .export-text-div { position: absolute; - margin-top: 0; - margin-left: 20px; + display: flex; + justify-content: flex-end; + align-items: center; top: calc(var(--offset-top) * 24px); left: calc(var(--offset-left) * 24px); + height: 24px; + margin-top: -5px; + margin-left: 20px; + .export-text { + } + .export-text-input { + } } - .import-text { + .import-text-div { position: absolute; - text-align: right; + display: flex; + justify-content: flex-end; + align-items: center; top: calc(var(--offset-top) * 24px); - left: calc(var(--offset-left) * 24px); - margin-top: 0; - margin-left: calc(-100px - 2px); - width: 100px; + left: calc(var(--offset-left) * 24px - 90px); + margin-top: -5px; + height: 24px; + //margin-left: calc(-80px - 2px); + width: 90px; + .import-text { + direction: rtl; + text-align: right; + } + .import-text-input { + text-align: right; + } } } diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index e1acdc891c..071e3cb621 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -12,7 +12,7 @@ use editor::consts::FILE_SAVE_SUFFIX; use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use editor::messages::portfolio::document::utility_types::network_interface::NodeTemplate; +use editor::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, NodeTemplate}; use editor::messages::portfolio::utility_types::Platform; use editor::messages::prelude::*; use editor::messages::tool::tool_messages::tool_prelude::WidgetId; @@ -697,6 +697,26 @@ impl EditorHandle { self.dispatch(DocumentMessage::SetToNodeOrLayer { node_id: NodeId(id), is_layer }); } + /// Set the name of an import or export + #[wasm_bindgen(js_name = setImportName)] + pub fn set_import_name(&self, index: usize, name: String) { + let message = NodeGraphMessage::SetImportExportName { + name, + index: ImportOrExport::Import(index), + }; + self.dispatch(message); + } + + /// Set the name of an export + #[wasm_bindgen(js_name = setExportName)] + pub fn set_export_name(&self, index: usize, name: String) { + let message = NodeGraphMessage::SetImportExportName { + name, + index: ImportOrExport::Export(index), + }; + self.dispatch(message); + } + #[wasm_bindgen(js_name = injectImaginatePollServerStatus)] pub fn inject_imaginate_poll_server_status(&self) { self.dispatch(PortfolioMessage::ImaginatePollServerStatus); From 1aab00094109b72a14aabf129a125a6cf2de8ab1 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 27 Dec 2024 00:20:24 -0800 Subject: [PATCH 17/19] improve UI --- Cargo.lock | 350 +++++++++--------- .../node_graph/document_node_definitions.rs | 3 +- .../document/node_graph/node_properties.rs | 40 +- .../utility_types/network_interface.rs | 23 +- frontend/src/components/views/Graph.svelte | 159 +++++--- node-graph/gcore/src/ops.rs | 2 +- 6 files changed, 317 insertions(+), 260 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 706ba1cfb6..8a829eacb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,12 +76,6 @@ dependencies = [ "alloc-no-stdlib", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android-activity" version = "0.5.2" @@ -181,9 +175,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arboard" @@ -233,9 +227,9 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener 5.3.1", "event-listener-strategy", @@ -345,7 +339,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -380,7 +374,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -432,7 +426,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "itoa 1.0.14", "matchit", @@ -653,9 +647,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "serde", @@ -669,22 +663,22 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -776,9 +770,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.3" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -1006,7 +1000,7 @@ dependencies = [ "gpu-executor", "graph-craft", "graphene-core", - "reqwest 0.12.9", + "reqwest 0.12.10", "serde_json", "wgpu-executor", ] @@ -1169,18 +1163,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1197,9 +1191,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -1241,7 +1235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1251,7 +1245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1281,7 +1275,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1292,7 +1286,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1330,7 +1324,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1343,7 +1337,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1391,7 +1385,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1446,7 +1440,7 @@ dependencies = [ "dyn-any-derive", "glam", "log", - "reqwest 0.12.9", + "reqwest 0.12.10", ] [[package]] @@ -1456,7 +1450,7 @@ dependencies = [ "dyn-any", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1518,14 +1512,14 @@ checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -1533,9 +1527,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -1710,6 +1704,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "font-types" version = "0.8.2" @@ -1769,7 +1769,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -1883,7 +1883,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -2278,13 +2278,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown 0.15.2", ] [[package]] @@ -2331,7 +2331,7 @@ dependencies = [ "js-sys", "log", "num-traits", - "reqwest 0.12.9", + "reqwest 0.12.10", "rustc-hash 2.1.0", "serde", "serde_json", @@ -2430,7 +2430,7 @@ dependencies = [ "path-bool", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.9", + "reqwest 0.12.10", "resvg", "rustc-hash 2.1.0", "serde", @@ -2494,7 +2494,7 @@ dependencies = [ "serde_json", "specta", "spin", - "thiserror 2.0.6", + "thiserror 2.0.9", "tokio", "usvg", "wasm-bindgen", @@ -2511,7 +2511,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -2656,21 +2656,14 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -2807,9 +2800,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -2831,9 +2824,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -2852,13 +2845,13 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http 1.2.0", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "rustls", "rustls-pki-types", @@ -2875,7 +2868,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.31", + "hyper 0.14.32", "native-tls", "tokio", "tokio-native-tls", @@ -2889,7 +2882,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-util", "native-tls", "tokio", @@ -2908,7 +2901,7 @@ dependencies = [ "futures-util", "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.5.2", "pin-project-lite", "socket2", "tokio", @@ -2938,7 +2931,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -3109,7 +3102,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -3502,9 +3495,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -3530,7 +3523,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -3730,9 +3723,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", @@ -3751,9 +3744,9 @@ dependencies = [ [[package]] name = "naga" -version = "23.0.0" +version = "23.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set", @@ -3909,7 +3902,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -3974,7 +3967,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4035,7 +4028,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4203,9 +4196,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -4255,7 +4248,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4297,9 +4290,9 @@ dependencies = [ [[package]] name = "os_info" -version = "3.9.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ca711d8b83edbb00b44d504503cd247c9c0bd8b0fa2694f2a1a3d8165379ce" +checksum = "eb6651f4be5e39563c4fe5cc8326349eb99a25d805a3493f791d5bfd0269e430" dependencies = [ "log", "serde", @@ -4380,7 +4373,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -4434,7 +4427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 2.0.6", + "thiserror 2.0.9", "ucd-trie", ] @@ -4458,7 +4451,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4586,7 +4579,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4694,9 +4687,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -4824,7 +4817,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -4903,7 +4896,7 @@ dependencies = [ "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 2.0.6", + "thiserror 2.0.9", "tokio", "tracing", ] @@ -4922,7 +4915,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.6", + "thiserror 2.0.9", "tinyvec", "tracing", "web-time 1.1.0", @@ -4930,9 +4923,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -4944,9 +4937,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -5078,9 +5071,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea267d65fcb21e813c5824611103d18a9d84754a718e3603f60740dce0e3515" +checksum = "be4b40e5383c077b9eb19c80c64d47d1369479a136aeae9a23c3ea43e970407b" dependencies = [ "bytemuck", "font-types", @@ -5097,9 +5090,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -5179,7 +5172,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-tls 0.5.0", "ipnet", "js-sys", @@ -5209,9 +5202,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "3d3536321cfc54baa8cf3e273d5e1f63f889067829c4b410fcdbac8ca7b80994" dependencies = [ "base64 0.22.1", "bytes", @@ -5223,7 +5216,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.5.2", "hyper-rustls", "hyper-tls 0.6.0", "hyper-util", @@ -5247,6 +5240,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls", + "tower", "tower-service", "url", "wasm-bindgen", @@ -5381,9 +5375,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring", @@ -5413,9 +5407,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" dependencies = [ "web-time 1.1.0", ] @@ -5433,9 +5427,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rustybuzz" @@ -5537,9 +5531,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5" dependencies = [ "core-foundation-sys", "libc", @@ -5567,18 +5561,18 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" dependencies = [ "serde", ] [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -5596,20 +5590,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "indexmap 2.7.0", "itoa 1.0.14", @@ -5636,7 +5630,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -5662,9 +5656,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", @@ -5680,14 +5674,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -5709,7 +5703,7 @@ checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -5807,9 +5801,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "skrifa" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79589c6a7f90a81270b6d4ced5c5e8dc77d3064e920adb35315ed79e82fbbe61" +checksum = "d3a16eb047396452019439e1d6eca13581c141275b7d743f0262f79d65c09c70" dependencies = [ "bytemuck", "read-fonts", @@ -5932,7 +5926,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -5956,7 +5950,7 @@ dependencies = [ [[package]] name = "spirv-std" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#1932353935338c0ac5b7b150fae0551ddeaa1dad" +source = "git+https://github.com/Rust-GPU/rust-gpu.git#bfa63c15a921357b38aa78986670c15f36df76dc" dependencies = [ "bitflags 1.3.2", "glam", @@ -5968,18 +5962,18 @@ dependencies = [ [[package]] name = "spirv-std-macros" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#1932353935338c0ac5b7b150fae0551ddeaa1dad" +source = "git+https://github.com/Rust-GPU/rust-gpu.git#bfa63c15a921357b38aa78986670c15f36df76dc" dependencies = [ "proc-macro2", "quote", "spirv-std-types", - "syn 1.0.109", + "syn 2.0.91", ] [[package]] name = "spirv-std-types" version = "0.9.0" -source = "git+https://github.com/Rust-GPU/rust-gpu.git#1932353935338c0ac5b7b150fae0551ddeaa1dad" +source = "git+https://github.com/Rust-GPU/rust-gpu.git#bfa63c15a921357b38aa78986670c15f36df76dc" [[package]] name = "stable_deref_trait" @@ -6084,9 +6078,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", @@ -6116,7 +6110,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -6251,7 +6245,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -6534,11 +6528,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.9", ] [[package]] @@ -6549,18 +6543,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -6663,9 +6657,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -6702,7 +6696,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -6808,14 +6802,14 @@ dependencies = [ [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -6870,7 +6864,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -6974,9 +6968,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-bidi-mirroring" @@ -7146,7 +7140,7 @@ dependencies = [ "png", "skrifa", "static_assertions", - "thiserror 2.0.6", + "thiserror 2.0.9", "vello_encoding", "vello_shaders", "wgpu", @@ -7171,7 +7165,7 @@ source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d83118 dependencies = [ "bytemuck", "naga", - "thiserror 2.0.6", + "thiserror 2.0.9", "vello_encoding", ] @@ -7265,7 +7259,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "wasm-bindgen-shared", ] @@ -7300,7 +7294,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7845,7 +7839,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -7856,7 +7850,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -7867,7 +7861,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -7878,7 +7872,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -8506,7 +8500,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "synstructure", ] @@ -8557,7 +8551,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "zvariant_utils", ] @@ -8590,7 +8584,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -8610,7 +8604,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "synstructure", ] @@ -8639,7 +8633,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] [[package]] @@ -8659,9 +8653,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] @@ -8688,7 +8682,7 @@ dependencies = [ "proc-macro-crate 3.2.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", "zvariant_utils", ] @@ -8700,5 +8694,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.91", ] 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 bbe50cdffb..4aefe6bfc8 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 @@ -2989,7 +2989,7 @@ pub static IMAGINATE_NODE: Lazy = Lazy::new(|| DocumentN }, description: Cow::Borrowed("TODO"), - properties: None, + properties: Some(&node_properties::imaginate_properties), }); fn name_to_properties(properties_string: &str) -> &'static (dyn Fn(NodeId, &mut NodePropertiesContext) -> Vec + Sync) { @@ -3000,6 +3000,7 @@ fn name_to_properties(properties_string: &str) -> &'static (dyn Fn(NodeId, &mut "offset_path_properties" => &node_properties::offset_path_properties, "selective_color_properties" => &node_properties::selective_color_properties, "exposure_properties" => &node_properties::exposure_properties, + "math_properties" => &node_properties::math_properties, "rectangle_properties" => &node_properties::rectangle_properties, _ => { log::error!("Node properties not found for {properties_string}"); 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 d8836a6f62..ab99e91009 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1532,7 +1532,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties ] } -pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { +pub(crate) fn imaginate_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { let imaginate_node = [context.selection_network_path, &[node_id]].concat(); let resolve_input = |name: &str| { @@ -1560,6 +1560,13 @@ pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId let faces_index = resolve_input("Improve Faces"); let tiling_index = resolve_input("Tiling"); + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in imaginate_properties: {err}"); + return Vec::new(); + } + }; let controller = &document_node.inputs[resolve_input("Controller")]; let server_status = { @@ -1770,13 +1777,19 @@ pub(crate) fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId ) .unwrap_or_default(); - let resolution = { - use graphene_std::imaginate::pick_safe_imaginate_resolution; + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in imaginate_properties: {err}"); + return Vec::new(); + } + }; + let resolution = { let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Number, false); let round = |size: DVec2| { - let (x, y) = pick_safe_imaginate_resolution(size.into()); + let (x, y) = graphene_std::imaginate::pick_safe_imaginate_resolution(size.into()); DVec2::new(x as f64, y as f64) }; @@ -2321,18 +2334,15 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte vec![LayoutGroup::Row { widgets: distance }, line_join, LayoutGroup::Row { widgets: miter_limit }] } -pub(crate) fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let location = vec2_widget(document_node, node_id, 2, "Location", "X", "Y", " px", None, add_blank_assist); - let dimensions = vec2_widget(document_node, node_id, 3, "Dimensions", "W", "H", " px", None, add_blank_assist); - let background = color_widget(document_node, node_id, 4, "Background", ColorButton::default().allow_none(false), true); - let clip = bool_widget(document_node, node_id, 5, "Clip", CheckboxInput::default(), true); - - let clip_row = LayoutGroup::Row { widgets: clip }; - - vec![location, dimensions, background, clip_row] -} +pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in offset_path_properties: {err}"); + return Vec::new(); + } + }; -pub fn math_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { let expression_index = 1; let operation_b_index = 2; 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 6f7b1d939f..e48ca1e9b5 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -4356,28 +4356,39 @@ impl NodeNetworkInterface { pub fn set_import_export_name(&mut self, name: String, index: ImportOrExport, network_path: &[NodeId]) { let Some(encapsulating_node) = self.encapsulating_node_metadata_mut(network_path) else { - log::error!("Could not get nested network in set_import_export_name"); + log::error!("Could not get encapsulating network in set_import_export_name"); return; }; - match index { + let name_changed = match index { ImportOrExport::Import(import_index) => { let Some(input_properties) = encapsulating_node.persistent_metadata.input_properties.get_mut(import_index) else { log::error!("Could not get input properties in set_import_export_name"); return; }; - input_properties.input_data.insert("input_name".to_string(), json!(name)); + // Only return true if the previous value is the same as the current value + input_properties + .input_data + .insert("input_name".to_string(), json!(name)) + .filter(|val| val.as_str().is_some_and(|old_name| *old_name == name)) + .is_none() } ImportOrExport::Export(export_index) => { let Some(export_name) = encapsulating_node.persistent_metadata.output_names.get_mut(export_index) else { log::error!("Could not get export_name in set_import_export_name"); return; }; - *export_name = name; + if *export_name == name { + false + } else { + *export_name = name; + true + } } + }; + if name_changed { + self.transaction_modified(); } - - self.transaction_modified(); } pub fn set_pinned(&mut self, node_id: &NodeId, network_path: &[NodeId], pinned: bool) { diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 250d6b99cc..2952795b75 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -1,6 +1,6 @@