From 1ed28c3e859e0ef8446df786628dd3676bb8d93d Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Sep 2025 21:17:49 -0700 Subject: [PATCH 1/8] Render overlay node --- .../src/messages/frontend/frontend_message.rs | 7 +- .../utility_types/widgets/button_widgets.rs | 2 +- .../node_graph/generate_node_graph_overlay.rs | 110 +++++++++++ .../portfolio/document/node_graph/mod.rs | 1 + .../node_graph/node_graph_message_handler.rs | 39 ++-- .../document/node_graph/node_properties.rs | 7 +- .../document/node_graph/utility_types.rs | 184 +----------------- .../utility_types/network_interface.rs | 2 +- .../network_interface/node_graph.rs | 26 +-- .../network_interface/resolved_types.rs | 27 ++- .../portfolio/document/utility_types/nodes.rs | 4 +- .../portfolio/document/utility_types/wires.rs | 3 +- editor/src/node_graph_executor.rs | 11 +- frontend/src/messages.ts | 3 + frontend/src/state-providers/node-graph.ts | 8 +- node-graph/gcore/src/lib.rs | 1 + node-graph/gcore/src/node_graph_overlay.rs | 47 +++++ .../gcore/src/node_graph_overlay/types.rs | 170 ++++++++++++++++ node-graph/gcore/src/types.rs | 4 +- node-graph/graph-craft/src/document/value.rs | 1 + node-graph/graster-nodes/src/adjustments.rs | 16 +- 21 files changed, 440 insertions(+), 233 deletions(-) create mode 100644 editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs create mode 100644 node-graph/gcore/src/node_graph_overlay.rs create mode 100644 node-graph/gcore/src/node_graph_overlay/types.rs diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 4661baf394..1948b38a3b 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,14 +1,13 @@ use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use crate::messages::app_window::app_window_message_handler::AppWindowPlatform; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::utility_types::{ - BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendExports, FrontendImport, FrontendNodeToRender, FrontendNodeType, FrontendXY, Transform, -}; +use crate::messages::portfolio::document::node_graph::utility_types::{BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendNodeType, Transform}; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; use crate::messages::portfolio::document::utility_types::wires::WirePathInProgress; use crate::messages::prelude::*; use crate::messages::tool::utility_types::HintData; use graph_craft::document::NodeId; +use graphene_std::node_graph_overlay::types::{FrontendExports, FrontendImport, FrontendNodeToRender, FrontendXY}; use graphene_std::raster::Image; use graphene_std::raster::color::Color; use graphene_std::text::{Font, TextAlign}; @@ -278,6 +277,8 @@ pub enum FrontendMessage { // Displays a dashed border around the node #[serde(rename = "previewedNode")] previewed_node: Option, + }, + UpdateNativeNodeGraphRender { #[serde(rename = "nativeNodeGraphRender")] native_node_graph_render: bool, }, diff --git a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs index 3698bf3bc0..5f401d08dd 100644 --- a/editor/src/messages/layout/utility_types/widgets/button_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/button_widgets.rs @@ -1,8 +1,8 @@ use crate::messages::input_mapper::utility_types::misc::ActionKeys; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; use crate::messages::tool::tool_messages::tool_prelude::WidgetCallback; use derivative::*; +use graphene_std::node_graph_overlay::types::FrontendGraphDataType; use graphene_std::vector::style::FillChoice; use graphite_proc_macros::WidgetBuilder; diff --git a/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs new file mode 100644 index 0000000000..6d2decf3d8 --- /dev/null +++ b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs @@ -0,0 +1,110 @@ +use graph_craft::{ + concrete, + document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork, value::TaggedValue}, +}; +use graphene_std::{ + Context, graphic, memo, + node_graph_overlay::{self, types::NodeGraphOverlayData}, + uuid::NodeId, +}; + +pub fn generate_node_graph_overlay(node_graph_overlay_data: NodeGraphOverlayData, opacity: f64) -> DocumentNode { + // TODO: Implement as Network and implement finer grained caching for the background, nodes, and exports + DocumentNode { + inputs: vec![ + NodeInput::value(TaggedValue::None, true), + NodeInput::value(TaggedValue::NodeGraphOverlayData(node_graph_overlay_data), true), + NodeInput::value(TaggedValue::F64(opacity), true), + ], + implementation: DocumentNodeImplementation::Network(NodeNetwork { + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: vec![ + // Merge the overlay on top of the artwork + ( + NodeId(0), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)], + implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), + ..Default::default() + }, + ), + //Wrap Artwork in a table + ( + NodeId(1), + DocumentNode { + inputs: vec![NodeInput::network(concrete!(Context), 0)], + implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER), + call_argument: concrete!(Context), + ..Default::default() + }, + ), + // Cache the full node graph so its not rerendered when the artwork changes + ( + NodeId(2), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::node(NodeId(3), 0)], + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), + ..Default::default() + }, + ), + // Merge the nodes on top of the dot grid background + ( + NodeId(3), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::node(NodeId(5), 0), NodeInput::node(NodeId(4), 0)], + implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), + ..Default::default() + }, + ), + // Generate the dot grid background + ( + NodeId(4), + DocumentNode { + inputs: vec![NodeInput::network(concrete!(Context), 2)], + implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::dot_grid_background::IDENTIFIER), + call_argument: concrete!(Context), + ..Default::default() + }, + ), + // Transform the nodes based on the Context + ( + NodeId(5), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::node(NodeId(6), 0)], + implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::transform_nodes::IDENTIFIER), + ..Default::default() + }, + ), + // Cache the nodes + ( + NodeId(6), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::node(NodeId(7), 0)], + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), + ..Default::default() + }, + ), + // Create the nodes + ( + NodeId(7), + DocumentNode { + call_argument: concrete!(Context), + inputs: vec![NodeInput::network(concrete!(Context), 1)], + implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::generate_nodes::IDENTIFIER), + ..Default::default() + }, + ), + ] + .into_iter() + .collect(), + ..Default::default() + }), + call_argument: concrete!(Context), + ..Default::default() + } +} diff --git a/editor/src/messages/portfolio/document/node_graph/mod.rs b/editor/src/messages/portfolio/document/node_graph/mod.rs index bd25016438..30874339f3 100644 --- a/editor/src/messages/portfolio/document/node_graph/mod.rs +++ b/editor/src/messages/portfolio/document/node_graph/mod.rs @@ -1,4 +1,5 @@ pub mod document_node_definitions; +pub mod generate_node_graph_overlay; mod node_graph_message; mod node_graph_message_handler; pub mod node_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 f337f5f1e8..f326def104 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 @@ -6,7 +6,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::document_message_handler::navigation_controls; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext; -use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, FrontendXY}; +use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction}; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::misc::GroupFolderType; use crate::messages::portfolio::document::utility_types::network_interface::{ @@ -21,9 +21,10 @@ use crate::messages::tool::common_functionality::utility_functions::make_path_ed use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2, IVec2}; -use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; +use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; +use graphene_std::node_graph_overlay::types::{FrontendGraphDataType, FrontendXY}; use graphene_std::vector::algorithms::bezpath_algorithms::bezpath_is_inside_bezpath; use graphene_std::*; use kurbo::{DEFAULT_ACCURACY, Shape}; @@ -95,6 +96,8 @@ pub struct NodeGraphMessageHandler { frontend_nodes: Vec, /// Disables rendering nodes in Svelte native_node_graph_render: bool, + /// The node which renders the node graph overlay. Inserted after the root export + pub node_graph_overlay: 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. @@ -877,7 +880,7 @@ impl<'a> MessageHandler> for NodeG }; let Some(output_connector) = output_connector else { return }; self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path); - self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&network_interface.input_type(clicked_input, breadcrumb_network_path)); + self.wire_in_progress_type = network_interface.input_type(clicked_input, breadcrumb_network_path).displayed_type(); return; } @@ -888,7 +891,7 @@ impl<'a> MessageHandler> for NodeG self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path); let output_type = network_interface.output_type(&clicked_output, breadcrumb_network_path); - self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type); + self.wire_in_progress_type = output_type.displayed_type(); self.update_node_graph_hints(responses); return; @@ -1626,14 +1629,24 @@ impl<'a> MessageHandler> for NodeG let nodes_to_render = network_interface.collect_nodes(&self.node_graph_errors, preferences.graph_wire_style, breadcrumb_network_path); self.frontend_nodes = nodes_to_render.iter().map(|node| node.metadata.node_id).collect(); let previewed_node = network_interface.previewed_node(breadcrumb_network_path); - responses.add(FrontendMessage::UpdateNodeGraphRender { - nodes_to_render, - open: graph_view_overlay_open, - opacity: graph_fade_artwork_percentage, - in_selected_network: selection_network_path == breadcrumb_network_path, - previewed_node, - native_node_graph_render: self.native_node_graph_render, - }); + if self.native_node_graph_render { + let node_graph_render_data = node_graph_overlay::types::NodeGraphOverlayData { + nodes_to_render, + open: graph_view_overlay_open, + in_selected_network: selection_network_path == breadcrumb_network_path, + previewed_node, + }; + self.node_graph_overlay = Some(super::generate_node_graph_overlay::generate_node_graph_overlay(node_graph_render_data, graph_fade_artwork_percentage)); + responses.add(PortfolioMessage::SubmitActiveGraphRender); + } else { + responses.add(FrontendMessage::UpdateNodeGraphRender { + nodes_to_render, + open: graph_view_overlay_open, + opacity: graph_fade_artwork_percentage, + in_selected_network: selection_network_path == breadcrumb_network_path, + previewed_node, + }); + } responses.add(NodeGraphMessage::UpdateVisibleNodes); let layer_widths = network_interface.collect_layer_widths(breadcrumb_network_path); @@ -1786,6 +1799,7 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::ToggleNativeNodeGraphRender => { self.native_node_graph_render = !self.native_node_graph_render; + self.node_graph_overlay = None; responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::ToggleSelectedLocked => { @@ -2620,6 +2634,7 @@ impl Default for NodeGraphMessageHandler { end_index: None, frontend_nodes: Vec::new(), native_node_graph_render: false, + node_graph_overlay: 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 971c6cfa2c..f29d8148b2 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -1,7 +1,6 @@ #![allow(clippy::too_many_arguments)] use super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext}; -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::*; @@ -14,6 +13,7 @@ use graph_craft::{Type, concrete}; use graphene_std::NodeInputDecleration; use graphene_std::animation::RealTimeMode; use graphene_std::extract_xy::XY; +use graphene_std::node_graph_overlay::types::FrontendGraphDataType; use graphene_std::path_bool::BooleanOperation; use graphene_std::raster::curve::Curve; use graphene_std::raster::{ @@ -1936,7 +1936,10 @@ pub struct ParameterWidgetsInfo<'a> { impl<'a> ParameterWidgetsInfo<'a> { pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path); - let input_type = FrontendGraphDataType::displayed_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path)); + let input_type = context + .network_interface + .input_type(&InputConnector::node(node_id, index), context.selection_network_path) + .displayed_type(); let document_node = context.network_interface.document_node(&node_id, context.selection_network_path); ParameterWidgetsInfo { 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 7551ad0734..5f51b07359 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -1,188 +1,6 @@ use graph_craft::document::NodeId; -use graph_craft::document::value::TaggedValue; -use graphene_std::Type; use std::borrow::Cow; -use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::TypeSource; - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum FrontendGraphDataType { - #[default] - General, - Number, - Artboard, - Graphic, - Raster, - Vector, - Color, - Gradient, - Typography, -} - -impl FrontendGraphDataType { - pub fn from_type(input: &Type) -> Self { - match TaggedValue::from_type_or_none(input) { - TaggedValue::U32(_) - | TaggedValue::U64(_) - | TaggedValue::F32(_) - | TaggedValue::F64(_) - | TaggedValue::DVec2(_) - | TaggedValue::F64Array4(_) - | TaggedValue::VecF64(_) - | TaggedValue::VecDVec2(_) - | TaggedValue::DAffine2(_) => Self::Number, - TaggedValue::Artboard(_) => Self::Artboard, - TaggedValue::Graphic(_) => Self::Graphic, - TaggedValue::Raster(_) => Self::Raster, - TaggedValue::Vector(_) => Self::Vector, - TaggedValue::Color(_) => Self::Color, - TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient, - TaggedValue::String(_) => Self::Typography, - _ => Self::General, - } - } - - pub fn displayed_type(type_source: &TypeSource) -> Self { - match type_source.compiled_nested_type() { - Some(nested_type) => Self::from_type(&nested_type), - None => Self::General, - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendXY { - pub x: i32, - pub y: i32, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendGraphInput { - #[serde(rename = "dataType")] - pub data_type: FrontendGraphDataType, - #[serde(rename = "resolvedType")] - pub resolved_type: String, - pub name: String, - pub description: String, - /// Either "nothing", "import index {index}", or "{node name} output {output_index}". - #[serde(rename = "connectedToString")] - pub connected_to: String, - /// Used to render the upstream node once this node is rendered - #[serde(rename = "connectedToNode")] - pub connected_to_node: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendGraphOutput { - #[serde(rename = "dataType")] - pub data_type: FrontendGraphDataType, - pub name: String, - #[serde(rename = "resolvedType")] - pub resolved_type: String, - pub description: String, - /// If connected to an export, it is "export index {index}". - /// If connected to a node, it is "{node name} input {input_index}". - #[serde(rename = "connectedTo")] - pub connected_to: Vec, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendExport { - pub port: FrontendGraphInput, - pub wire: Option, -} - -#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendExports { - /// If the primary export is not visible, then it is None. - pub exports: Vec>, - #[serde(rename = "previewWire")] - pub preview_wire: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendImport { - pub port: FrontendGraphOutput, - pub wires: Vec, -} - -// Metadata that is common to nodes and layers -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendNodeMetadata { - #[serde(rename = "nodeId")] - pub node_id: NodeId, - // TODO: Remove and replace with popup manager system - #[serde(rename = "canBeLayer")] - pub can_be_layer: bool, - #[serde(rename = "displayName")] - pub display_name: String, - pub selected: bool, - // Used to get the description, which is stored in a global hashmap - pub reference: Option, - // Reduces opacity of node/hidden eye icon - pub visible: bool, - // The svg string for each input - // pub wires: Vec>, - pub errors: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendNode { - // pub position: FrontendNodePosition, - pub position: FrontendXY, - pub inputs: Vec>, - pub outputs: Vec>, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendLayer { - #[serde(rename = "bottomInput")] - pub bottom_input: FrontendGraphInput, - #[serde(rename = "sideInput")] - pub side_input: Option, - pub output: FrontendGraphOutput, - // pub position: FrontendLayerPosition, - pub position: FrontendXY, - pub locked: bool, - #[serde(rename = "chainWidth")] - pub chain_width: u32, - #[serde(rename = "layerHasLeftBorderGap")] - pub layer_has_left_border_gap: bool, - #[serde(rename = "primaryInputConnectedToLayer")] - pub primary_input_connected_to_layer: bool, - #[serde(rename = "primaryOutputConnectedToLayer")] - pub primary_output_connected_to_layer: bool, -} - -// // Should be an enum but those are hard to serialize/deserialize to TS -// #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -// pub struct FrontendNodePosition { -// pub absolute: Option, -// pub chain: Option, -// } - -// // Should be an enum but those are hard to serialize/deserialize to TS -// #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -// pub struct FrontendLayerPosition { -// pub absolute: Option, -// pub stack: Option, -// } - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendNodeOrLayer { - pub node: Option, - pub layer: Option, -} - -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendNodeToRender { - pub metadata: FrontendNodeMetadata, - #[serde(rename = "nodeOrLayer")] - pub node_or_layer: FrontendNodeOrLayer, - //TODO: Remove - pub wires: Vec<(String, bool, FrontendGraphDataType)>, -} - #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendNodeType { pub name: Cow<'static, str>, @@ -208,7 +26,7 @@ impl FrontendNodeType { } } } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DragStart { pub start_x: f64, pub start_y: f64, 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 af8c15dff5..b6debcd013 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -5288,7 +5288,7 @@ impl InputConnector { } /// Represents an output connector -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum OutputConnector { #[serde(rename = "node")] Node { diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs index c06e65d40a..ee56dbe896 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/node_graph.rs @@ -1,19 +1,19 @@ use glam::{DVec2, IVec2}; use graph_craft::proto::GraphErrors; -use graphene_std::uuid::NodeId; +use graphene_std::{ + node_graph_overlay::types::{ + FrontendExport, FrontendExports, FrontendGraphInput, FrontendGraphOutput, FrontendImport, FrontendLayer, FrontendNode, FrontendNodeMetadata, FrontendNodeOrLayer, FrontendNodeToRender, + FrontendXY, + }, + uuid::NodeId, +}; use kurbo::BezPath; 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}, - messages::portfolio::document::{ - node_graph::utility_types::{ - FrontendExport, FrontendExports, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput, FrontendImport, FrontendLayer, FrontendNode, FrontendNodeMetadata, FrontendNodeOrLayer, - FrontendNodeToRender, FrontendXY, - }, - utility_types::{ - network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector, Previewing}, - wires::{GraphWireStyle, build_vector_wire}, - }, + messages::portfolio::document::utility_types::{ + network_interface::{FlowType, InputConnector, NodeNetworkInterface, OutputConnector, Previewing}, + wires::{GraphWireStyle, build_vector_wire}, }, }; @@ -116,7 +116,7 @@ impl NodeNetworkInterface { ( wire, self.wire_is_thick(&InputConnector::node(node_id, input_index), network_path), - FrontendGraphDataType::displayed_type(&self.input_type(&InputConnector::node(node_id, input_index), network_path)), + self.input_type(&InputConnector::node(node_id, input_index), network_path).displayed_type(), ) }) }) @@ -136,7 +136,7 @@ impl NodeNetworkInterface { return None; } let input_type = self.input_type(input_connector, network_path); - let data_type = FrontendGraphDataType::displayed_type(&input_type); + let data_type = input_type.displayed_type(); let resolved_type = input_type.resolved_type_name(); let connected_to = self @@ -239,7 +239,7 @@ impl NodeNetworkInterface { (import_name, description) } }; - let data_type = FrontendGraphDataType::displayed_type(&output_type); + let data_type = output_type.displayed_type(); let resolved_type = output_type.resolved_type_name(); let mut connected_to = self .outward_wires(network_path) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs index 1e099c7ca1..bc04dc1bc7 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface/resolved_types.rs @@ -4,7 +4,7 @@ use graph_craft::{ ProtoNodeIdentifier, Type, concrete, document::{DocumentNodeImplementation, InlineRust, NodeInput, value::TaggedValue}, }; -use graphene_std::uuid::NodeId; +use graphene_std::{node_graph_overlay::types::FrontendGraphDataType, uuid::NodeId}; use interpreted_executor::{ dynamic_executor::{NodeTypes, ResolvedDocumentNodeTypesDelta}, node_registry::NODE_REGISTRY, @@ -45,6 +45,31 @@ pub enum TypeSource { } impl TypeSource { + pub fn displayed_type(&self) -> FrontendGraphDataType { + match self.compiled_nested_type() { + Some(nested_type) => match TaggedValue::from_type_or_none(nested_type) { + TaggedValue::U32(_) + | TaggedValue::U64(_) + | TaggedValue::F32(_) + | TaggedValue::F64(_) + | TaggedValue::DVec2(_) + | TaggedValue::F64Array4(_) + | TaggedValue::VecF64(_) + | TaggedValue::VecDVec2(_) + | TaggedValue::DAffine2(_) => FrontendGraphDataType::Number, + TaggedValue::Artboard(_) => FrontendGraphDataType::Artboard, + TaggedValue::Graphic(_) => FrontendGraphDataType::Graphic, + TaggedValue::Raster(_) => FrontendGraphDataType::Raster, + TaggedValue::Vector(_) => FrontendGraphDataType::Vector, + TaggedValue::Color(_) => FrontendGraphDataType::Color, + TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => FrontendGraphDataType::Gradient, + TaggedValue::String(_) => FrontendGraphDataType::Typography, + _ => FrontendGraphDataType::General, + }, + None => FrontendGraphDataType::General, + } + } + pub fn into_compiled_nested_type(self) -> Option { match self { TypeSource::Compiled(compiled_type) => Some(compiled_type.into_nested_type()), diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index c120938a80..f262e4bdb5 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -62,7 +62,7 @@ pub struct LayerPanelEntry { } /// IMPORTANT: the same node may appear multiple times. -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct SelectedNodes(pub Vec); impl SelectedNodes { @@ -172,5 +172,5 @@ impl SelectedNodes { } } -#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)] +#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct CollapsedLayers(pub Vec); diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs index 869bcb8890..29e62d9466 100644 --- a/editor/src/messages/portfolio/document/utility_types/wires.rs +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -1,6 +1,5 @@ -use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; use glam::{DVec2, IVec2}; -use graphene_std::vector::misc::dvec2_to_point; +use graphene_std::{node_graph_overlay::types::FrontendGraphDataType, vector::misc::dvec2_to_point}; use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape}; #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index b279203635..6a669a22ae 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -116,10 +116,17 @@ impl NodeGraphExecutor { /// Update the cached network if necessary. fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option, ignore_hash: bool) -> Result<(), String> { - let network_hash = document.network_interface.document_network().current_hash(); + let mut network = document.network_interface.document_network().clone(); + if let Some(mut node_graph_overlay_node) = document.node_graph_handler.node_graph_overlay.clone() { + let node_graph_overlay_id = NodeId::new(); + let new_export = NodeInput::node(node_graph_overlay_id, 0); + let old_export = std::mem::replace(&mut network.exports[0], new_export); + node_graph_overlay_node.inputs[0] = old_export; + network.nodes.insert(node_graph_overlay_id, node_graph_overlay_node); + } + let network_hash = network.current_hash(); // Refresh the graph when it changes or the inspect node changes if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash { - let network = document.network_interface.document_network().clone(); self.previous_node_to_inspect = node_to_inspect; self.node_graph_hash = network_hash; diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 509ce7aec7..273e6f7924 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -117,7 +117,9 @@ export class UpdateNodeGraphRender extends JsMessage { readonly inSelectedNetwork!: boolean; readonly previewedNode!: bigint | undefined; +} +export class UpdateNativeNodeGraphRender extends JsMessage { readonly nativeNodeGraphRender!: boolean; } @@ -1739,6 +1741,7 @@ export const messageMakers: Record = { UpdateMouseCursor, UpdateNodeGraphControlBarLayout, UpdateNodeGraphRender, + UpdateNativeNodeGraphRender, UpdateNodeGraphSelectionBox, UpdateNodeGraphTransform, UpdateNodeThumbnail, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index 55abce5022..becd70ae53 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -16,6 +16,7 @@ import { UpdateImportsExports, UpdateLayerWidths, UpdateNodeGraphRender, + UpdateNativeNodeGraphRender, UpdateVisibleNodes, UpdateNodeGraphTransform, UpdateNodeThumbnail, @@ -120,7 +121,12 @@ export function createNodeGraphState(editor: Editor) { state.opacity = updateNodeGraphRender.opacity; state.inSelectedNetwork = updateNodeGraphRender.inSelectedNetwork; state.previewedNode = updateNodeGraphRender.previewedNode; - state.nativeNodeGraphRender = updateNodeGraphRender.nativeNodeGraphRender; + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateNativeNodeGraphRender, (updateNativeNodeGraphRender) => { + update((state) => { + state.nativeNodeGraphRender = updateNativeNodeGraphRender.nativeNodeGraphRender; return state; }); }); diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 3b098147ff..5701729bd5 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -16,6 +16,7 @@ pub mod logic; pub mod math; pub mod memo; pub mod misc; +pub mod node_graph_overlay; pub mod ops; pub mod raster; pub mod raster_types; diff --git a/node-graph/gcore/src/node_graph_overlay.rs b/node-graph/gcore/src/node_graph_overlay.rs new file mode 100644 index 0000000000..01d6e8a32e --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay.rs @@ -0,0 +1,47 @@ +use graphene_core_shaders::{Ctx, color::Color}; +use kurbo::{BezPath, Point}; + +use crate::{ExtractFootprint, table::Table, vector::Vector}; + +pub mod types; + +#[node_macro::node(category(""))] +pub fn generate_nodes(_: impl Ctx, _node_graph_overlay_data: types::NodeGraphOverlayData) -> Table { + Table::new() +} + +#[node_macro::node(category(""))] +pub fn transform_nodes(_ctx: impl Ctx + ExtractFootprint, nodes: Table) -> Table { + nodes +} + +#[node_macro::node(category(""))] +pub fn dot_grid_background(ctx: impl Ctx + ExtractFootprint, opacity: f64) -> Table { + let Some(footprint) = ctx.try_footprint() else { + log::error!("Could not get footprint from context in dot_grid_background"); + return Table::new(); + }; + // From --color-2-mildblack: --color-2-mildblack-rgb: 34, 34, 34; + let gray = (34. / 255.) as f32; + let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, opacity as f32) else { + log::error!("Could not create color in dot grid background"); + return Table::new(); + }; + + let mut bez_path = BezPath::new(); + let p0 = Point::new(0., 0.); // bottom-left + let p1 = Point::new(footprint.resolution.x as f64, 0.); // bottom-right + let p2 = Point::new(footprint.resolution.x as f64, footprint.resolution.y as f64); // top-right + let p3 = Point::new(0., footprint.resolution.y as f64); // top-left + + bez_path.move_to(p0); + bez_path.line_to(p1); + bez_path.line_to(p2); + bez_path.line_to(p3); + bez_path.close_path(); + + let mut vector = Vector::from_bezpath(bez_path); + vector.style.fill = crate::vector::style::Fill::Solid(bg_color); + + Table::new_from_element(vector) +} diff --git a/node-graph/gcore/src/node_graph_overlay/types.rs b/node-graph/gcore/src/node_graph_overlay/types.rs new file mode 100644 index 0000000000..db9a293dae --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay/types.rs @@ -0,0 +1,170 @@ +use crate::uuid::NodeId; + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct NodeGraphOverlayData { + pub nodes_to_render: Vec, + pub open: bool, + pub in_selected_network: bool, + // Displays a dashed border around the node + pub previewed_node: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct FrontendNodeToRender { + pub metadata: FrontendNodeMetadata, + #[serde(rename = "nodeOrLayer")] + pub node_or_layer: FrontendNodeOrLayer, + //TODO: Remove + pub wires: Vec<(String, bool, FrontendGraphDataType)>, +} + +// Metadata that is common to nodes and layers +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendNodeMetadata { + #[serde(rename = "nodeId")] + pub node_id: NodeId, + // TODO: Remove and replace with popup manager system + #[serde(rename = "canBeLayer")] + pub can_be_layer: bool, + #[serde(rename = "displayName")] + pub display_name: String, + pub selected: bool, + // Used to get the description, which is stored in a global hashmap + pub reference: Option, + // Reduces opacity of node/hidden eye icon + pub visible: bool, + // The svg string for each input + // pub wires: Vec>, + pub errors: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendNode { + // pub position: FrontendNodePosition, + pub position: FrontendXY, + pub inputs: Vec>, + pub outputs: Vec>, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendLayer { + #[serde(rename = "bottomInput")] + pub bottom_input: FrontendGraphInput, + #[serde(rename = "sideInput")] + pub side_input: Option, + pub output: FrontendGraphOutput, + // pub position: FrontendLayerPosition, + pub position: FrontendXY, + pub locked: bool, + #[serde(rename = "chainWidth")] + pub chain_width: u32, + #[serde(rename = "layerHasLeftBorderGap")] + pub layer_has_left_border_gap: bool, + #[serde(rename = "primaryInputConnectedToLayer")] + pub primary_input_connected_to_layer: bool, + #[serde(rename = "primaryOutputConnectedToLayer")] + pub primary_output_connected_to_layer: bool, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendXY { + pub x: i32, + pub y: i32, +} + +// // Should be an enum but those are hard to serialize/deserialize to TS +// #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +// pub struct FrontendNodePosition { +// pub absolute: Option, +// pub chain: Option, +// } + +// // Should be an enum but those are hard to serialize/deserialize to TS +// #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +// pub struct FrontendLayerPosition { +// pub absolute: Option, +// pub stack: Option, +// } + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendNodeOrLayer { + pub node: Option, + pub layer: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendGraphInput { + #[serde(rename = "dataType")] + pub data_type: FrontendGraphDataType, + #[serde(rename = "resolvedType")] + pub resolved_type: String, + pub name: String, + pub description: String, + /// Either "nothing", "import index {index}", or "{node name} output {output_index}". + #[serde(rename = "connectedToString")] + pub connected_to: String, + /// Used to render the upstream node once this node is rendered + #[serde(rename = "connectedToNode")] + pub connected_to_node: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendGraphOutput { + #[serde(rename = "dataType")] + pub data_type: FrontendGraphDataType, + pub name: String, + #[serde(rename = "resolvedType")] + pub resolved_type: String, + pub description: String, + /// If connected to an export, it is "export index {index}". + /// If connected to a node, it is "{node name} input {input_index}". + #[serde(rename = "connectedTo")] + pub connected_to: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendExport { + pub port: FrontendGraphInput, + pub wire: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendExports { + /// If the primary export is not visible, then it is None. + pub exports: Vec>, + #[serde(rename = "previewWire")] + pub preview_wire: Option, +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] + +pub struct FrontendImport { + pub port: FrontendGraphOutput, + pub wires: Vec, +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum FrontendGraphDataType { + #[default] + General, + Number, + Artboard, + Graphic, + Raster, + Vector, + Color, + Gradient, + Typography, +} diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 133cb88c1d..741f1f88b9 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -126,7 +126,7 @@ impl std::fmt::Debug for NodeIOTypes { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash, specta::Type, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub struct ProtoNodeIdentifier { pub name: Cow<'static, str>, } @@ -230,7 +230,7 @@ impl PartialEq for TypeDescriptor { } /// Graph runtime type information used for type inference. -#[derive(Clone, PartialEq, Eq, Hash, specta::Type, serde::Serialize, serde::Deserialize)] +#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] pub enum Type { /// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type. Generic(Cow<'static, str>), diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 1af121e8ed..327d02a81c 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -259,6 +259,7 @@ tagged_value! { CentroidType(graphene_core::vector::misc::CentroidType), BooleanOperation(graphene_path_bool::BooleanOperation), TextAlign(graphene_core::text::TextAlign), + NodeGraphOverlayData(graphene_core::node_graph_overlay::types::NodeGraphOverlayData), } impl TaggedValue { diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 1274ef5fe4..574bbc57da 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -549,7 +549,7 @@ pub enum RedGreenBlue { /// Color Channel #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] #[widget(Radio)] pub enum RedGreenBlueAlpha { #[default] @@ -561,7 +561,7 @@ pub enum RedGreenBlueAlpha { /// Style of noise pattern #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] pub enum NoiseType { #[default] @@ -577,7 +577,7 @@ pub enum NoiseType { } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] /// Style of layered levels of the noise pattern pub enum FractalType { #[default] @@ -594,7 +594,7 @@ pub enum FractalType { /// Distance function used by the cellular noise #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] pub enum CellularDistanceFunction { #[default] Euclidean, @@ -605,7 +605,7 @@ pub enum CellularDistanceFunction { } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] pub enum CellularReturnType { CellValue, #[default] @@ -625,7 +625,7 @@ pub enum CellularReturnType { /// Type of domain warp #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] pub enum DomainWarpType { #[default] @@ -737,7 +737,7 @@ fn channel_mixer>( } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] #[widget(Radio)] pub enum RelativeAbsolute { #[default] @@ -747,7 +747,7 @@ pub enum RelativeAbsolute { #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] pub enum SelectiveColorChoice { #[default] Reds, From 6ddd5cf4f89b838375d581464c88a3c6108daaf1 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 10:56:33 -0700 Subject: [PATCH 2/8] clean up --- .../node_graph/node_graph_message_handler.rs | 6 +----- editor/src/node_graph_executor.rs | 11 ++--------- node-graph/graster-nodes/src/adjustments.rs | 16 ++++++++-------- 3 files changed, 11 insertions(+), 22 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 f326def104..6c9dc192c0 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 @@ -96,8 +96,6 @@ pub struct NodeGraphMessageHandler { frontend_nodes: Vec, /// Disables rendering nodes in Svelte native_node_graph_render: bool, - /// The node which renders the node graph overlay. Inserted after the root export - pub node_graph_overlay: 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. @@ -1636,8 +1634,7 @@ impl<'a> MessageHandler> for NodeG in_selected_network: selection_network_path == breadcrumb_network_path, previewed_node, }; - self.node_graph_overlay = Some(super::generate_node_graph_overlay::generate_node_graph_overlay(node_graph_render_data, graph_fade_artwork_percentage)); - responses.add(PortfolioMessage::SubmitActiveGraphRender); + let _ = Some(super::generate_node_graph_overlay::generate_node_graph_overlay(node_graph_render_data, graph_fade_artwork_percentage)); } else { responses.add(FrontendMessage::UpdateNodeGraphRender { nodes_to_render, @@ -1799,7 +1796,6 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::ToggleNativeNodeGraphRender => { self.native_node_graph_render = !self.native_node_graph_render; - self.node_graph_overlay = None; responses.add(NodeGraphMessage::SendGraph); } NodeGraphMessage::ToggleSelectedLocked => { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 6a669a22ae..b279203635 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -116,17 +116,10 @@ impl NodeGraphExecutor { /// Update the cached network if necessary. fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option, ignore_hash: bool) -> Result<(), String> { - let mut network = document.network_interface.document_network().clone(); - if let Some(mut node_graph_overlay_node) = document.node_graph_handler.node_graph_overlay.clone() { - let node_graph_overlay_id = NodeId::new(); - let new_export = NodeInput::node(node_graph_overlay_id, 0); - let old_export = std::mem::replace(&mut network.exports[0], new_export); - node_graph_overlay_node.inputs[0] = old_export; - network.nodes.insert(node_graph_overlay_id, node_graph_overlay_node); - } - let network_hash = network.current_hash(); + let network_hash = document.network_interface.document_network().current_hash(); // Refresh the graph when it changes or the inspect node changes if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash { + let network = document.network_interface.document_network().clone(); self.previous_node_to_inspect = node_to_inspect; self.node_graph_hash = network_hash; diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 574bbc57da..1274ef5fe4 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -549,7 +549,7 @@ pub enum RedGreenBlue { /// Color Channel #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Radio)] pub enum RedGreenBlueAlpha { #[default] @@ -561,7 +561,7 @@ pub enum RedGreenBlueAlpha { /// Style of noise pattern #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] pub enum NoiseType { #[default] @@ -577,7 +577,7 @@ pub enum NoiseType { } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] /// Style of layered levels of the noise pattern pub enum FractalType { #[default] @@ -594,7 +594,7 @@ pub enum FractalType { /// Distance function used by the cellular noise #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] pub enum CellularDistanceFunction { #[default] Euclidean, @@ -605,7 +605,7 @@ pub enum CellularDistanceFunction { } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] pub enum CellularReturnType { CellValue, #[default] @@ -625,7 +625,7 @@ pub enum CellularReturnType { /// Type of domain warp #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Dropdown)] pub enum DomainWarpType { #[default] @@ -737,7 +737,7 @@ fn channel_mixer>( } #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] #[widget(Radio)] pub enum RelativeAbsolute { #[default] @@ -747,7 +747,7 @@ pub enum RelativeAbsolute { #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, node_macro::ChoiceType)] -#[cfg_attr(feature = "std", derive(dyn_any::DynAny, serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(dyn_any::DynAny, specta::Type, serde::Serialize, serde::Deserialize))] pub enum SelectiveColorChoice { #[default] Reds, From 30dbae72ca9b34cec4fdaf4988556901d01f9892 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 11:27:53 -0700 Subject: [PATCH 3/8] Improve native UI network --- .../src/messages/frontend/frontend_message.rs | 18 +- .../document/document_message_handler.rs | 4 +- .../node_graph/generate_node_graph_overlay.rs | 117 +++++++----- .../node_graph/node_graph_message_handler.rs | 23 +-- .../document/node_graph/utility_types.rs | 7 - editor/src/test_utils.rs | 2 +- frontend/src/components/Editor.svelte | 1 + frontend/src/messages.ts | 15 +- frontend/src/state-providers/node-graph.ts | 28 ++- .../gcore-shaders/src/color/color_types.rs | 21 +++ node-graph/gcore/src/node_graph_overlay.rs | 60 ++++-- .../gcore/src/node_graph_overlay/consts.rs | 42 +++++ .../src/node_graph_overlay/nodes_and_wires.rs | 175 ++++++++++++++++++ .../gcore/src/node_graph_overlay/types.rs | 84 +++++++-- .../src/node_graph_overlay/ui_context.rs | 26 +++ node-graph/graph-craft/src/proto.rs | 2 +- node-graph/gstd/src/wasm_application_io.rs | 23 +++ .../interpreted-executor/src/node_registry.rs | 8 + 18 files changed, 537 insertions(+), 119 deletions(-) create mode 100644 node-graph/gcore/src/node_graph_overlay/consts.rs create mode 100644 node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs create mode 100644 node-graph/gcore/src/node_graph_overlay/ui_context.rs diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 1948b38a3b..358ca2e3ba 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,13 +1,13 @@ use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use crate::messages::app_window::app_window_message_handler::AppWindowPlatform; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::utility_types::{BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendNodeType, Transform}; +use crate::messages::portfolio::document::node_graph::utility_types::{BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendNodeType}; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; use crate::messages::portfolio::document::utility_types::wires::WirePathInProgress; use crate::messages::prelude::*; use crate::messages::tool::utility_types::HintData; use graph_craft::document::NodeId; -use graphene_std::node_graph_overlay::types::{FrontendExports, FrontendImport, FrontendNodeToRender, FrontendXY}; +use graphene_std::node_graph_overlay::types::{FrontendExports, FrontendImport, FrontendNodeToRender, FrontendXY, NodeGraphTransform}; use graphene_std::raster::Image; use graphene_std::raster::color::Color; use graphene_std::text::{Font, TextAlign}; @@ -267,7 +267,7 @@ pub enum FrontendMessage { UpdateMouseCursor { cursor: MouseCursorIcon, }, - UpdateNodeGraphRender { + UpdateNodeGraphSvelteRender { #[serde(rename = "nodesToRender")] nodes_to_render: Vec, open: bool, @@ -278,9 +278,13 @@ pub enum FrontendMessage { #[serde(rename = "previewedNode")] previewed_node: Option, }, - UpdateNativeNodeGraphRender { - #[serde(rename = "nativeNodeGraphRender")] - native_node_graph_render: bool, + UpdateShouldRenderSvelteNodes { + #[serde(rename = "shouldRenderSvelteNodes")] + should_render_svelte_nodes: bool, + }, + UpdateNativeNodeGraphSVG { + #[serde(rename = "svgString")] + svg_string: String, }, UpdateVisibleNodes { nodes: Vec, @@ -291,7 +295,7 @@ pub enum FrontendMessage { diff: Vec, }, UpdateNodeGraphTransform { - transform: Transform, + transform: NodeGraphTransform, }, UpdateNodeThumbnail { id: NodeId, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 32a73a4496..5430726c7e 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1,5 +1,4 @@ use super::node_graph::document_node_definitions; -use super::node_graph::utility_types::Transform; use super::overlays::utility_types::Pivot; use super::utility_types::error::EditorError; use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState}; @@ -31,6 +30,7 @@ use glam::{DAffine2, DVec2, IVec2}; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork}; use graphene_std::math::quad::Quad; +use graphene_std::node_graph_overlay::types::NodeGraphTransform; use graphene_std::path_bool::{boolean_intersect, path_bool_lib}; use graphene_std::raster::BlendMode; use graphene_std::raster_types::Raster; @@ -1481,7 +1481,7 @@ impl MessageHandler> for DocumentMes responses.add(NodeGraphMessage::UpdateImportsExports); responses.add(FrontendMessage::UpdateNodeGraphTransform { - transform: Transform { + transform: NodeGraphTransform { scale: transform.matrix2.x_axis.x, x: transform.translation.x, y: transform.translation.y, diff --git a/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs index 6d2decf3d8..d3d7a9f237 100644 --- a/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs +++ b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs @@ -3,108 +3,133 @@ use graph_craft::{ document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork, value::TaggedValue}, }; use graphene_std::{ - Context, graphic, memo, - node_graph_overlay::{self, types::NodeGraphOverlayData}, + node_graph_overlay::{types::NodeGraphOverlayData, ui_context::UIContext}, + table::Table, uuid::NodeId, }; +/// https://excalidraw.com/#json=LgKS6I4lQvGPmke06ZJyp,D9aON9vVZJAjNnZWfwy_SQ pub fn generate_node_graph_overlay(node_graph_overlay_data: NodeGraphOverlayData, opacity: f64) -> DocumentNode { - // TODO: Implement as Network and implement finer grained caching for the background, nodes, and exports + let generate_nodes_id = NodeId::new(); + let cache_nodes_id = NodeId::new(); + let transform_nodes_id = NodeId::new(); + + let generate_node_graph_bg = NodeId::new(); + let cache_node_graph_bg = NodeId::new(); + + let merge_nodes_and_bg_id = NodeId::new(); + let render_overlay_id = NodeId::new(); + let send_overlay_id = NodeId::new(); + let _cache_output_id = NodeId::new(); + // TODO: Replace with new cache node + let memo_implementation = DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER); + DocumentNode { inputs: vec![ - NodeInput::value(TaggedValue::None, true), + NodeInput::value(TaggedValue::Vector(Table::new()), true), NodeInput::value(TaggedValue::NodeGraphOverlayData(node_graph_overlay_data), true), NodeInput::value(TaggedValue::F64(opacity), true), ], + implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(0), 0)], + exports: vec![NodeInput::node(send_overlay_id, 0)], nodes: vec![ - // Merge the overlay on top of the artwork + // Create the nodes ( - NodeId(0), + generate_nodes_id, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::network(concrete!(UIContext), 1)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::GenerateNodesNode".into()), ..Default::default() }, ), - //Wrap Artwork in a table + // Cache the nodes ( - NodeId(1), + cache_nodes_id, DocumentNode { - inputs: vec![NodeInput::network(concrete!(Context), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER), - call_argument: concrete!(Context), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(generate_nodes_id, 0)], + implementation: memo_implementation.clone(), ..Default::default() }, ), - // Cache the full node graph so its not rerendered when the artwork changes + // Transform the nodes based on the Context ( - NodeId(2), + transform_nodes_id, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::node(NodeId(3), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(cache_nodes_id, 0)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::TransformNodesNode".into()), ..Default::default() }, ), - // Merge the nodes on top of the dot grid background + // Generate the dot grid background ( - NodeId(3), + generate_node_graph_bg, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::node(NodeId(5), 0), NodeInput::node(NodeId(4), 0)], - implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER), + inputs: vec![NodeInput::network(concrete!(UIContext), 2)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::DotGridBackgroundNode".into()), + call_argument: concrete!(UIContext), ..Default::default() }, ), - // Generate the dot grid background + // Cache the dot grid background ( - NodeId(4), + cache_node_graph_bg, DocumentNode { - inputs: vec![NodeInput::network(concrete!(Context), 2)], - implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::dot_grid_background::IDENTIFIER), - call_argument: concrete!(Context), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(generate_node_graph_bg, 0)], + implementation: memo_implementation.clone(), ..Default::default() }, ), - // Transform the nodes based on the Context + // Merge the nodes on top of the dot grid background ( - NodeId(5), + merge_nodes_and_bg_id, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::node(NodeId(6), 0)], - implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::transform_nodes::IDENTIFIER), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(transform_nodes_id, 0), NodeInput::node(cache_node_graph_bg, 0)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::NodeGraphUiExtendNode".into()), ..Default::default() }, ), - // Cache the nodes + // Render the node graph UI graphic to an SVG ( - NodeId(6), + render_overlay_id, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::node(NodeId(7), 0)], - implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(merge_nodes_and_bg_id, 0)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_std::wasm_application_io::RenderNodeGraphUiNode".into()), ..Default::default() }, ), - // Create the nodes + // Send the overlay to the frontend ( - NodeId(7), + send_overlay_id, DocumentNode { - call_argument: concrete!(Context), - inputs: vec![NodeInput::network(concrete!(Context), 1)], - implementation: DocumentNodeImplementation::ProtoNode(node_graph_overlay::generate_nodes::IDENTIFIER), + call_argument: concrete!(UIContext), + inputs: vec![NodeInput::node(render_overlay_id, 0)], + implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::SendRenderNode".into()), ..Default::default() }, ), + // Cache the full node graph so its not rerendered when the artwork changes + // ( + // cache_nodes_id, + // DocumentNode { + // call_argument: concrete!(UIContext), + // inputs: vec![NodeInput::node(send_overlay_id, 0)], + // implementation: memo_implementation.clone(), + // ..Default::default() + // }, + // ), ] .into_iter() .collect(), ..Default::default() }), - call_argument: concrete!(Context), + call_argument: concrete!(UIContext), ..Default::default() } } 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 6c9dc192c0..41c78536f7 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 @@ -21,7 +21,7 @@ use crate::messages::tool::common_functionality::utility_functions::make_path_ed use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion}; use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2, IVec2}; -use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput}; +use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; use graph_craft::proto::GraphErrors; use graphene_std::math::math_ext::QuadExt; use graphene_std::node_graph_overlay::types::{FrontendGraphDataType, FrontendXY}; @@ -1624,19 +1624,13 @@ impl<'a> MessageHandler> for NodeG responses.add(DocumentMessage::DocumentStructureChanged); responses.add(PropertiesPanelMessage::Refresh); responses.add(NodeGraphMessage::UpdateActionButtons); - let nodes_to_render = network_interface.collect_nodes(&self.node_graph_errors, preferences.graph_wire_style, breadcrumb_network_path); - self.frontend_nodes = nodes_to_render.iter().map(|node| node.metadata.node_id).collect(); - let previewed_node = network_interface.previewed_node(breadcrumb_network_path); + if self.native_node_graph_render { - let node_graph_render_data = node_graph_overlay::types::NodeGraphOverlayData { - nodes_to_render, - open: graph_view_overlay_open, - in_selected_network: selection_network_path == breadcrumb_network_path, - previewed_node, - }; - let _ = Some(super::generate_node_graph_overlay::generate_node_graph_overlay(node_graph_render_data, graph_fade_artwork_percentage)); } else { - responses.add(FrontendMessage::UpdateNodeGraphRender { + let nodes_to_render = network_interface.collect_nodes(&self.node_graph_errors, preferences.graph_wire_style, breadcrumb_network_path); + self.frontend_nodes = nodes_to_render.iter().map(|node| node.metadata.node_id).collect(); + let previewed_node = network_interface.previewed_node(breadcrumb_network_path); + responses.add(FrontendMessage::UpdateNodeGraphSvelteRender { nodes_to_render, open: graph_view_overlay_open, opacity: graph_fade_artwork_percentage, @@ -1796,7 +1790,11 @@ impl<'a> MessageHandler> for NodeG } NodeGraphMessage::ToggleNativeNodeGraphRender => { self.native_node_graph_render = !self.native_node_graph_render; + responses.add(FrontendMessage::UpdateShouldRenderSvelteNodes { + should_render_svelte_nodes: self.native_node_graph_render, + }); responses.add(NodeGraphMessage::SendGraph); + responses.add(MenuBarMessage::SendLayout); } NodeGraphMessage::ToggleSelectedLocked => { let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { @@ -2630,7 +2628,6 @@ impl Default for NodeGraphMessageHandler { end_index: None, frontend_nodes: Vec::new(), native_node_graph_render: false, - node_graph_overlay: None, } } } 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 5f51b07359..3e49855647 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -34,13 +34,6 @@ pub struct DragStart { pub round_y: i32, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct Transform { - pub scale: f64, - pub x: f64, - pub y: f64, -} - #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct BoxSelection { #[serde(rename = "startX")] diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index f917a8b003..9bbb022da4 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -301,7 +301,7 @@ pub trait FrontendMessageTestUtils { impl FrontendMessageTestUtils for FrontendMessage { fn check_node_graph_error(&self) { - let FrontendMessage::UpdateNodeGraphRender { nodes_to_render, .. } = self else { return }; + let FrontendMessage::UpdateNodeGraphSvelteRender { nodes_to_render, .. } = self else { return }; for node in nodes_to_render { if let Some(error) = &node.metadata.errors { diff --git a/frontend/src/components/Editor.svelte b/frontend/src/components/Editor.svelte index a9b71310c4..2c4054ab0b 100644 --- a/frontend/src/components/Editor.svelte +++ b/frontend/src/components/Editor.svelte @@ -74,6 +74,7 @@ // Replace usage of `-rgb` variants with CSS color() function to calculate alpha when browsers support it // See https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color() and https://caniuse.com/css-color-function // Specifically, support for the relative syntax is needed: `color(from var(--color-0-black) srgb r g b / 0.5)` to convert black to 50% alpha + // Keep in sync with node_graph_overlay/consts.rs --color-0-black: #000; --color-0-black-rgb: 0, 0, 0; --color-1-nearblack: #111; diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 273e6f7924..9537da8c02 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -107,7 +107,7 @@ export class UpdateLayerWidths extends JsMessage { readonly layerWidths!: Map; } -export class UpdateNodeGraphRender extends JsMessage { +export class UpdateNodeGraphSvelteRender extends JsMessage { readonly nodesToRender!: FrontendNodeToRender[]; readonly open!: boolean; @@ -119,8 +119,12 @@ export class UpdateNodeGraphRender extends JsMessage { readonly previewedNode!: bigint | undefined; } -export class UpdateNativeNodeGraphRender extends JsMessage { - readonly nativeNodeGraphRender!: boolean; +export class UpdateShouldRenderSvelteNodes extends JsMessage { + readonly shouldRenderSvelteNodes!: boolean; +} + +export class UpdateNativeNodeGraphSVG extends JsMessage { + readonly svgString!: string; } export class UpdateVisibleNodes extends JsMessage { @@ -1740,8 +1744,9 @@ export const messageMakers: Record = { UpdateMenuBarLayout, UpdateMouseCursor, UpdateNodeGraphControlBarLayout, - UpdateNodeGraphRender, - UpdateNativeNodeGraphRender, + UpdateNodeGraphSvelteRender, + UpdateShouldRenderSvelteNodes, + UpdateNativeNodeGraphSVG, UpdateNodeGraphSelectionBox, UpdateNodeGraphTransform, UpdateNodeThumbnail, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index becd70ae53..ea34074d2d 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -15,8 +15,9 @@ import { UpdateExportReorderIndex, UpdateImportsExports, UpdateLayerWidths, - UpdateNodeGraphRender, - UpdateNativeNodeGraphRender, + UpdateNodeGraphSvelteRender, + UpdateShouldRenderSvelteNodes, + UpdateNativeNodeGraphSVG, UpdateVisibleNodes, UpdateNodeGraphTransform, UpdateNodeThumbnail, @@ -34,6 +35,7 @@ export function createNodeGraphState(editor: Editor) { updateImportsExports: undefined as UpdateImportsExports | undefined, reorderImportIndex: undefined as number | undefined, reorderExportIndex: undefined as number | undefined, + nativeNodeGraphSVGString: "", contextMenuInformation: undefined as ContextMenuInformation | undefined, nodeTypes: [] as FrontendNodeType[], @@ -48,7 +50,7 @@ export function createNodeGraphState(editor: Editor) { // TODO: Remove these fields visibleNodes: new Set(), layerWidths: new Map(), - nativeNodeGraphRender: false, + shouldRenderSvelteNodes: false, // Data that will be passed in the context thumbnails: new Map(), @@ -112,21 +114,27 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); - editor.subscriptions.subscribeJsMessage(UpdateNodeGraphRender, (updateNodeGraphRender) => { + editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSvelteRender, (updateNodeGraphSvelteRender) => { update((state) => { state.nodesToRender.clear(); - updateNodeGraphRender.nodesToRender.forEach((node) => { + updateNodeGraphSvelteRender.nodesToRender.forEach((node) => { state.nodesToRender.set(node.metadata.nodeId, node); }); - state.opacity = updateNodeGraphRender.opacity; - state.inSelectedNetwork = updateNodeGraphRender.inSelectedNetwork; - state.previewedNode = updateNodeGraphRender.previewedNode; + state.opacity = updateNodeGraphSvelteRender.opacity; + state.inSelectedNetwork = updateNodeGraphSvelteRender.inSelectedNetwork; + state.previewedNode = updateNodeGraphSvelteRender.previewedNode; return state; }); }); - editor.subscriptions.subscribeJsMessage(UpdateNativeNodeGraphRender, (updateNativeNodeGraphRender) => { + editor.subscriptions.subscribeJsMessage(UpdateShouldRenderSvelteNodes, (UpdateShouldRenderSvelteNodes) => { update((state) => { - state.nativeNodeGraphRender = updateNativeNodeGraphRender.nativeNodeGraphRender; + state.shouldRenderSvelteNodes = UpdateShouldRenderSvelteNodes.shouldRenderSvelteNodes; + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateNativeNodeGraphSVG, (updateNativeNodeGraphRender) => { + update((state) => { + state.nativeNodeGraphSVGString = updateNativeNodeGraphRender.svgString; return state; }); }); diff --git a/node-graph/gcore-shaders/src/color/color_types.rs b/node-graph/gcore-shaders/src/color/color_types.rs index 2cb4d231bd..32a73f938c 100644 --- a/node-graph/gcore-shaders/src/color/color_types.rs +++ b/node-graph/gcore-shaders/src/color/color_types.rs @@ -425,6 +425,16 @@ impl Color { Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha) } + pub fn from_rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Color { + let map_range = |int_color| int_color as f32 / 255.; + + let red = map_range(red); + let green = map_range(green); + let blue = map_range(blue); + let alpha = map_range(alpha); + Color { red, green, blue, alpha } + } + /// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1) /// /// # Examples @@ -938,6 +948,17 @@ impl Color { Some(Color::from_rgb8_srgb(r, g, b)) } + pub fn from_rgba8_no_srgb(color_str: &str) -> Option { + if color_str.len() != 6 { + return None; + } + let r = u8::from_str_radix(&color_str[0..2], 16).ok()?; + let g = u8::from_str_radix(&color_str[2..4], 16).ok()?; + let b = u8::from_str_radix(&color_str[4..6], 16).ok()?; + let a = 255; + Some(Color::from_rgba8(r, g, b, a)) + } + /// Linearly interpolates between two colors based on t. /// /// T must be between 0 and 1. diff --git a/node-graph/gcore/src/node_graph_overlay.rs b/node-graph/gcore/src/node_graph_overlay.rs index 01d6e8a32e..1e878a1491 100644 --- a/node-graph/gcore/src/node_graph_overlay.rs +++ b/node-graph/gcore/src/node_graph_overlay.rs @@ -1,38 +1,55 @@ use graphene_core_shaders::{Ctx, color::Color}; use kurbo::{BezPath, Point}; -use crate::{ExtractFootprint, table::Table, vector::Vector}; +use crate::{ + node_graph_overlay::{ + nodes_and_wires::{draw_layers, draw_nodes}, + types::NodeGraphOverlayData, + ui_context::{UIContext, UIRuntimeResponse}, + }, + table::Table, + transform::ApplyTransform, + vector::Vector, +}; +pub mod consts; +pub mod nodes_and_wires; pub mod types; +pub mod ui_context; -#[node_macro::node(category(""))] -pub fn generate_nodes(_: impl Ctx, _node_graph_overlay_data: types::NodeGraphOverlayData) -> Table { - Table::new() +#[node_macro::node(skip_impl)] +pub fn generate_nodes(_: impl Ctx, node_graph_overlay_data: NodeGraphOverlayData) -> Table { + let mut nodes_and_wires = Table::new(); + let layers = draw_layers(&node_graph_overlay_data.nodes_to_render); + nodes_and_wires.extend(layers); + + let nodes = draw_nodes(&node_graph_overlay_data.nodes_to_render); + nodes_and_wires.extend(nodes); + + nodes_and_wires } -#[node_macro::node(category(""))] -pub fn transform_nodes(_ctx: impl Ctx + ExtractFootprint, nodes: Table) -> Table { +#[node_macro::node(skip_impl)] +pub fn transform_nodes(ui_context: UIContext, mut nodes: Table) -> Table { + let matrix = ui_context.transform.to_daffine2(); + nodes.apply_transform(&matrix); nodes } -#[node_macro::node(category(""))] -pub fn dot_grid_background(ctx: impl Ctx + ExtractFootprint, opacity: f64) -> Table { - let Some(footprint) = ctx.try_footprint() else { - log::error!("Could not get footprint from context in dot_grid_background"); - return Table::new(); - }; +#[node_macro::node(skip_impl)] +pub fn dot_grid_background(ui_context: UIContext, opacity: f64) -> Table { // From --color-2-mildblack: --color-2-mildblack-rgb: 34, 34, 34; let gray = (34. / 255.) as f32; - let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, opacity as f32) else { + let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, (opacity / 100.) as f32) else { log::error!("Could not create color in dot grid background"); return Table::new(); }; let mut bez_path = BezPath::new(); let p0 = Point::new(0., 0.); // bottom-left - let p1 = Point::new(footprint.resolution.x as f64, 0.); // bottom-right - let p2 = Point::new(footprint.resolution.x as f64, footprint.resolution.y as f64); // top-right - let p3 = Point::new(0., footprint.resolution.y as f64); // top-left + let p1 = Point::new(ui_context.resolution.x as f64, 0.); // bottom-right + let p2 = Point::new(ui_context.resolution.x as f64, ui_context.resolution.y as f64); // top-right + let p3 = Point::new(0., ui_context.resolution.y as f64); // top-left bez_path.move_to(p0); bez_path.line_to(p1); @@ -45,3 +62,14 @@ pub fn dot_grid_background(ctx: impl Ctx + ExtractFootprint, opacity: f64) -> Ta Table::new_from_element(vector) } + +#[node_macro::node(skip_impl)] +pub fn node_graph_ui_extend(_: impl Ctx, new: Table, mut base: Table) -> Table { + base.extend(new); + base +} + +#[node_macro::node(skip_impl)] +pub fn send_render(ui_context: UIContext, render: String) -> () { + let _ = ui_context.response_sender.send(UIRuntimeResponse::OverlaySVG(render)); +} diff --git a/node-graph/gcore/src/node_graph_overlay/consts.rs b/node-graph/gcore/src/node_graph_overlay/consts.rs new file mode 100644 index 0000000000..220d260ee8 --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay/consts.rs @@ -0,0 +1,42 @@ +pub const GRID_SIZE: f64 = 24.; +pub const BEZ_PATH_TOLERANCE: f64 = 0.1; + +// Keep in sync with colors in Editor.svelte +pub const COLOR_0_BLACK: &str = "000000"; +pub const COLOR_1_NEARBLACK: &str = "111111"; +pub const COLOR_2_MILDBLACK: &str = "222222"; +pub const COLOR_3_DARKGRAY: &str = "333333"; +pub const COLOR_4_DIMGRAY: &str = "444444"; +pub const COLOR_5_DULLGRAY: &str = "555555"; +pub const COLOR_6_LOWERGRAY: &str = "666666"; +pub const COLOR_7_MIDDLEGRAY: &str = "777777"; +pub const COLOR_8_UPPERGRAY: &str = "888888"; +pub const COLOR_9_PALEGRAY: &str = "999999"; +pub const COLOR_A_SOFTGRAY: &str = "AAAAAA"; +pub const COLOR_B_LIGHTGRAY: &str = "BBBBBB"; +pub const COLOR_C_BRIGHTGRAY: &str = "CCCCCC"; +pub const COLOR_D_MILDWHITE: &str = "DDDDDD"; +pub const COLOR_E_NEARWHITE: &str = "EEEEEE"; +pub const COLOR_F_WHITE: &str = "FFFFFF"; + +pub const COLOR_ERROR_RED: &str = "D6536E"; +pub const COLOR_WARNING_YELLOW: &str = "D5AA43"; + +pub const COLOR_DATA_GENERAL: &str = "CFCFCF"; +pub const COLOR_DATA_GENERAL_DIM: &str = "8A8A8A"; +pub const COLOR_DATA_NUMBER: &str = "C9A699"; +pub const COLOR_DATA_NUMBER_DIM: &str = "886B60"; +pub const COLOR_DATA_ARTBOARD: &str = "FBF9EB"; +pub const COLOR_DATA_ARTBOARD_DIM: &str = "B9B9A9"; +pub const COLOR_DATA_GRAPHIC: &str = "68C587"; +pub const COLOR_DATA_GRAPHIC_DIM: &str = "37754C"; +pub const COLOR_DATA_RASTER: &str = "E4BB72"; +pub const COLOR_DATA_RASTER_DIM: &str = "9A7B43"; +pub const COLOR_DATA_VECTOR: &str = "65BBE5"; +pub const COLOR_DATA_VECTOR_DIM: &str = "417892"; +pub const COLOR_DATA_COLOR: &str = "CE6EA7"; +pub const COLOR_DATA_COLOR_DIM: &str = "924071"; +pub const COLOR_DATA_GRADIENT: &str = "AF81EB"; +pub const COLOR_DATA_GRADIENT_DIM: &str = "6C489B"; +pub const COLOR_DATA_TYPOGRAPHY: &str = "EEA7A7"; +pub const COLOR_DATA_TYPOGRAPHY_DIM: &str = "955252"; diff --git a/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs new file mode 100644 index 0000000000..494e782f40 --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs @@ -0,0 +1,175 @@ +use graphene_core_shaders::color::{AlphaMut, Color}; +use kurbo::{BezPath, RoundedRect, Shape}; + +use crate::{ + node_graph_overlay::{ + consts::*, + types::{FrontendGraphDataType, FrontendNodeToRender}, + }, + table::{Table, TableRow}, + vector::{Vector, style::Fill}, +}; + +pub fn draw_nodes(nodes: &Vec) -> Table { + let mut node_table = Table::new(); + for node_to_render in nodes { + if let Some(frontend_node) = node_to_render.node_or_layer.node.as_ref() { + let x = frontend_node.position.x as f64 * GRID_SIZE; + let y = frontend_node.position.y as f64 * GRID_SIZE + GRID_SIZE / 2.; + let w = GRID_SIZE * 5.0; + let number_of_exposed_inputs = frontend_node.inputs.iter().skip(1).filter(|x| x.is_some()).count(); + let height = 1 + number_of_exposed_inputs; + let h = height as f64 * GRID_SIZE; + + let border_rect = RoundedRect::new(x, y, x + w, y + h, 2.); + let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE); + let mut border_vector = Vector::from_bezpath(bez_path); + let primary_output_color = frontend_node.outputs[0] + .as_ref() + .map(|primary_output| primary_output.data_type.data_color_dim()) + .unwrap_or(FrontendGraphDataType::General.data_color_dim()); + let border_color = Color::from_rgba8_no_srgb(primary_output_color).unwrap(); + border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + let node_color = if node_to_render.metadata.selected { + let mut selection_color = Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap(); + selection_color.set_alpha(0.15); + selection_color + } else { + let mut bg_color = Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap(); + bg_color.set_alpha(0.33); + bg_color + }; + border_vector.style.fill = crate::vector::style::Fill::Solid(node_color); + + // Make primary input brighter + if number_of_exposed_inputs == 0 { + // Draw the first row with rounded bottom corners + node_table.push(TableRow::new_from_element(node_first_row(x, y, true))); + } else { + // Draw the first row without rounded bottom corners + node_table.push(TableRow::new_from_element(node_first_row(x, y, false))); + // for node_index in 0..(number_of_exposed_inputs - 1) { + // node_table.push(TableRow::new_from_element(node_secondary_row(x, y, node_index + 1, false))); + // } + // // Draw the last row with bottom corners + // node_table.push(TableRow::new_from_element(node_secondary_row(x, y, number_of_exposed_inputs, true))); + }; + + node_table.push(TableRow::new_from_element(border_vector)); + } + } + node_table +} + +pub fn draw_layers(nodes: &Vec) -> Table { + let mut layer_table = Table::new(); + for node_to_render in nodes { + if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() { + let chain_width = if frontend_layer.chain_width > 0 { + frontend_layer.chain_width as f64 * GRID_SIZE + 0.5 * GRID_SIZE + } else { + 0. + }; + + let x0 = frontend_layer.position.x as f64 * GRID_SIZE - chain_width + 0.5 * GRID_SIZE; + let y0 = frontend_layer.position.y as f64 * GRID_SIZE; + let h = 2. * GRID_SIZE; + let w = chain_width + 8. * GRID_SIZE - 0.5 * GRID_SIZE; + + let rect = RoundedRect::new(x0, y0, x0 + w, y0 + h, 8.); + let bez_path = rect.to_path(BEZ_PATH_TOLERANCE); + let mut vector = Vector::from_bezpath(bez_path); + let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap(); + vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + let mut background = if node_to_render.metadata.selected { + Color::from_rgba8_no_srgb(COLOR_6_LOWERGRAY).unwrap() + } else { + Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap() + }; + background.set_alpha(0.33); + vector.style.fill = crate::vector::style::Fill::Solid(background); + layer_table.push(TableRow::new_from_element(vector)); + } + } + layer_table +} + +fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector { + let x1 = x0 + GRID_SIZE * 5.; + let y1 = y0 + GRID_SIZE; + let r = 2.; + + let bez_path = if rounded_bottom { + let mut path = BezPath::new(); + // Start at bottom-left + path.move_to((x0, y1)); + + // Left side up + path.line_to((x0, y0 + r)); + + // Top-left corner arc + path.quad_to((x0, y0), (x0 + r, y0)); + + // Top edge + path.line_to((x1 - r, y0)); + + // Top-right corner arc + path.quad_to((x1, y0), (x1, y0 + r)); + + // Right side down + path.line_to((x1, y1)); + + // Bottom edge + path.line_to((x0, y1)); + + path.close_path(); + path + } else { + RoundedRect::new(x0, y0, x1, y1, r).to_path(BEZ_PATH_TOLERANCE) + }; + + let mut vector = Vector::from_bezpath(bez_path); + let mut color = Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap(); + color.set_alpha(0.05); + vector.style.fill = Fill::Solid(color); + vector +} + +// fn node_secondary_row(x0: f64, y: f64, index: usize, rounded_bottom: bool) -> Vector { +// let y0 = y + index as f64 * GRID_SIZE; +// let x1 = x0 + GRID_SIZE * 5.; +// let y1 = y0 + GRID_SIZE; +// let r = 2.; +// let bez_path = if rounded_bottom { +// let mut path = BezPath::new(); +// path.move_to((x0, y0)); + +// // Top edge +// path.line_to((x1, y0)); + +// // Right side down +// path.line_to((x1, y1 - r)); + +// // Bottom-right corner arc +// path.quad_to((x1, y1), (x1 - r, y1)); + +// // Bottom edge +// path.line_to((x0 + r, y1)); + +// // Bottom-left corner arc +// path.quad_to((x0, y1), (x0, y1 - r)); + +// // Left side up +// path.line_to((x0, y0)); + +// path.close_path(); +// path +// } else { +// Rect::new(x0, y0, x1, y1).to_path(BEZ_PATH_TOLERANCE) +// }; +// let mut vector = Vector::from_bezpath(bez_path); +// let mut color = Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap(); +// color.set_alpha(0.33); +// vector.style.fill = Fill::Solid(color); +// vector +// } diff --git a/node-graph/gcore/src/node_graph_overlay/types.rs b/node-graph/gcore/src/node_graph_overlay/types.rs index db9a293dae..8c52d02f62 100644 --- a/node-graph/gcore/src/node_graph_overlay/types.rs +++ b/node-graph/gcore/src/node_graph_overlay/types.rs @@ -1,7 +1,30 @@ -use crate::uuid::NodeId; +use glam::{DAffine2, DVec2}; -#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] +use crate::{node_graph_overlay::consts::*, uuid::NodeId}; +use std::hash::{Hash, Hasher}; + +#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct NodeGraphTransform { + pub scale: f64, + pub x: f64, + pub y: f64, +} + +impl NodeGraphTransform { + pub fn to_daffine2(&self) -> DAffine2 { + DAffine2::from_scale_angle_translation(DVec2::splat(self.scale), 0.0, DVec2::new(self.x, self.y)) + } +} + +impl Hash for NodeGraphTransform { + fn hash(&self, state: &mut H) { + self.scale.to_bits().hash(state); + self.x.to_bits().hash(state); + self.y.to_bits().hash(state); + } +} +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] pub struct NodeGraphOverlayData { pub nodes_to_render: Vec, pub open: bool, @@ -21,7 +44,6 @@ pub struct FrontendNodeToRender { // Metadata that is common to nodes and layers #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendNodeMetadata { #[serde(rename = "nodeId")] pub node_id: NodeId, @@ -41,7 +63,6 @@ pub struct FrontendNodeMetadata { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendNode { // pub position: FrontendNodePosition, pub position: FrontendXY, @@ -50,7 +71,6 @@ pub struct FrontendNode { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendLayer { #[serde(rename = "bottomInput")] pub bottom_input: FrontendGraphInput, @@ -71,7 +91,6 @@ pub struct FrontendLayer { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendXY { pub x: i32, pub y: i32, @@ -93,15 +112,32 @@ pub struct FrontendXY { // pub stack: Option, // } +// Should be an enum but those are hard to serialize/deserialize to TS #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendNodeOrLayer { pub node: Option, pub layer: Option, } -#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] +impl FrontendNodeOrLayer { + pub fn to_enum(self) -> NodeOrLayer { + let node_or_layer = if let Some(node) = self.node { + Some(NodeOrLayer::Node(node)) + } else if let Some(layer) = self.layer { + Some(NodeOrLayer::Layer(layer)) + } else { + None + }; + node_or_layer.unwrap() + } +} +pub enum NodeOrLayer { + Node(FrontendNode), + Layer(FrontendLayer), +} + +#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendGraphInput { #[serde(rename = "dataType")] pub data_type: FrontendGraphDataType, @@ -118,7 +154,6 @@ pub struct FrontendGraphInput { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendGraphOutput { #[serde(rename = "dataType")] pub data_type: FrontendGraphDataType, @@ -140,7 +175,6 @@ pub struct FrontendExport { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendExports { /// If the primary export is not visible, then it is None. pub exports: Vec>, @@ -149,7 +183,6 @@ pub struct FrontendExports { } #[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] - pub struct FrontendImport { pub port: FrontendGraphOutput, pub wires: Vec, @@ -168,3 +201,32 @@ pub enum FrontendGraphDataType { Gradient, Typography, } + +impl FrontendGraphDataType { + pub fn data_color(&self) -> &'static str { + match self { + FrontendGraphDataType::General => COLOR_DATA_GENERAL, + FrontendGraphDataType::Number => COLOR_DATA_NUMBER, + FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD, + FrontendGraphDataType::Graphic => COLOR_DATA_GRAPHIC, + FrontendGraphDataType::Raster => COLOR_DATA_RASTER, + FrontendGraphDataType::Vector => COLOR_DATA_VECTOR, + FrontendGraphDataType::Color => COLOR_DATA_COLOR, + FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT, + FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY, + } + } + pub fn data_color_dim(&self) -> &'static str { + match self { + FrontendGraphDataType::General => COLOR_DATA_GENERAL_DIM, + FrontendGraphDataType::Number => COLOR_DATA_NUMBER_DIM, + FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD_DIM, + FrontendGraphDataType::Graphic => COLOR_DATA_GRAPHIC_DIM, + FrontendGraphDataType::Raster => COLOR_DATA_RASTER_DIM, + FrontendGraphDataType::Vector => COLOR_DATA_VECTOR_DIM, + FrontendGraphDataType::Color => COLOR_DATA_COLOR_DIM, + FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT_DIM, + FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY_DIM, + } + } +} diff --git a/node-graph/gcore/src/node_graph_overlay/ui_context.rs b/node-graph/gcore/src/node_graph_overlay/ui_context.rs new file mode 100644 index 0000000000..62ca8aa858 --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay/ui_context.rs @@ -0,0 +1,26 @@ +use std::sync::{Arc, mpsc::Sender}; + +use glam::UVec2; +use graphene_core_shaders::{Ctx, context::ArcCtx}; + +use crate::node_graph_overlay::types::NodeGraphTransform; + +pub type UIContext = Arc; + +#[derive(Debug, Clone, dyn_any::DynAny)] +pub struct UIContextImpl { + pub transform: NodeGraphTransform, + pub resolution: UVec2, + pub response_sender: Sender, +} + +#[derive(Debug, Clone, dyn_any::DynAny)] +pub enum UIRuntimeResponse { + RuntimeReady, + OverlaySVG(String), + OverlayTexture(wgpu::Texture), + // OverlayClickTargets(NodeId, ClickTarget) +} + +impl Ctx for UIContextImpl {} +impl ArcCtx for UIContextImpl {} diff --git a/node-graph/graph-craft/src/proto.rs b/node-graph/graph-craft/src/proto.rs index 9fefb03572..3d86f97404 100644 --- a/node-graph/graph-craft/src/proto.rs +++ b/node-graph/graph-craft/src/proto.rs @@ -546,7 +546,7 @@ impl TypingContext { // If the node has a value input we can infer the return type from it ConstructionArgs::Value(ref v) => { // TODO: This should return a reference to the value - let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]); + let types = NodeIOTypes::new(generic!(T), Type::Future(Box::new(v.ty())), vec![]); self.inferred.insert(node_id, types.clone()); return Ok(types); } diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 91771417a4..019e60aa2e 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -1,3 +1,4 @@ +use glam::DVec2; use graph_craft::document::value::RenderOutput; pub use graph_craft::document::value::RenderOutputType; pub use graph_craft::wasm_application_io::*; @@ -6,6 +7,7 @@ use graphene_core::Artboard; use graphene_core::gradient::GradientStops; #[cfg(target_family = "wasm")] use graphene_core::math::bbox::Bbox; +use graphene_core::node_graph_overlay::ui_context::UIContext; use graphene_core::raster::image::Image; use graphene_core::raster_types::{CPU, Raster}; use graphene_core::table::Table; @@ -349,3 +351,24 @@ async fn render<'a: 'n, T: 'n + Render + WasmNotSend>( }; RenderOutput { data, metadata } } + +#[node_macro::node(skip_impl)] +async fn render_node_graph_ui( + ui_context: UIContext, + #[implementations( + UIContext -> Table, + UIContext -> Table, + UIContext -> Table, + UIContext -> Table>, + UIContext -> Table, + UIContext -> Table, + )] + data: impl Node, +) -> String { + let data = data.eval(ui_context.clone()).await; + let render_params = RenderParams::default(); + let mut render = SvgRender::new(); + data.render_svg(&mut render, &render_params); + render.format_svg(DVec2::ZERO, ui_context.resolution.as_dvec2()); + render.svg.to_svg_string() +} diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 2f90dca139..50b40d0d0b 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -21,6 +21,8 @@ use graphene_std::application_io::{ImageTexture, SurfaceFrame}; use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; +use graphene_std::node_graph_overlay::types::NodeGraphOverlayData; +use graphene_std::node_graph_overlay::ui_context::UIContext; use graphene_std::table::Table; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; @@ -183,6 +185,12 @@ fn node_registry() -> HashMap, input: UIContext, fn_params: [UIContext => NodeGraphOverlayData]), + async_node!(graphene_core::node_graph_overlay::TransformNodesNode<_>, input: UIContext, fn_params: [UIContext =>Table]), + async_node!(graphene_core::node_graph_overlay::DotGridBackgroundNode<_>, input: UIContext, fn_params: [UIContext =>f64]), + async_node!(graphene_core::node_graph_overlay::NodeGraphUiExtendNode<_, _>, input: UIContext, fn_params: [UIContext =>Table, UIContext =>Table]), + async_node!(graphene_std::wasm_application_io::RenderNodeGraphUiNode<_>, input: UIContext, fn_params: [UIContext =>Table]), + async_node!(graphene_core::node_graph_overlay::SendRenderNode<_>, input: UIContext, fn_params: [UIContext => String]), ]; // ============= // CONVERT NODES From 45d96719190baf4f95172eca74da4f643f107862 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 12:08:04 -0700 Subject: [PATCH 4/8] Function to generate overlay network --- editor/src/application.rs | 28 +++++++++++++++++++ editor/src/dispatcher.rs | 4 +-- .../document/document_message_handler.rs | 4 +-- .../menu_bar/menu_bar_message_handler.rs | 8 +++--- frontend/src/components/views/Graph.svelte | 12 +++++++- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/editor/src/application.rs b/editor/src/application.rs index fdb8c60334..feafacd379 100644 --- a/editor/src/application.rs +++ b/editor/src/application.rs @@ -1,5 +1,8 @@ use crate::dispatcher::Dispatcher; +use crate::messages::portfolio::document::node_graph::generate_node_graph_overlay::generate_node_graph_overlay; use crate::messages::prelude::*; +use graph_craft::document::{NodeInput, NodeNetwork}; +use graphene_std::node_graph_overlay::types::NodeGraphOverlayData; pub use graphene_std::uuid::*; // TODO: serialize with serde to save the current editor state @@ -30,6 +33,31 @@ impl Editor { pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque) -> Result<(), String> { self.dispatcher.poll_node_graph_evaluation(responses) } + + pub fn generate_node_graph_overlay_network(&mut self) -> Option { + let Some(active_document) = self.dispatcher.message_handlers.portfolio_message_handler.active_document_mut() else { + return None; + }; + let breadcrumb_network_path = &active_document.breadcrumb_network_path; + let nodes_to_render = active_document.network_interface.collect_nodes( + &active_document.node_graph_handler.node_graph_errors, + self.dispatcher.message_handlers.preferences_message_handler.graph_wire_style, + breadcrumb_network_path, + ); + let previewed_node = active_document.network_interface.previewed_node(breadcrumb_network_path); + let node_graph_render_data = NodeGraphOverlayData { + nodes_to_render, + open: active_document.graph_view_overlay_open, + in_selected_network: &active_document.selection_network_path == breadcrumb_network_path, + previewed_node, + }; + let node_graph_overlay_node = generate_node_graph_overlay(node_graph_render_data, active_document.graph_fade_artwork_percentage); + Some(NodeNetwork { + exports: vec![NodeInput::node(NodeId(0), 0)], + nodes: vec![(NodeId(0), node_graph_overlay_node)].into_iter().collect(), + ..Default::default() + }) + } } impl Default for Editor { diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 50fd43e2c4..c8f8cd4b48 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -20,11 +20,11 @@ pub struct DispatcherMessageHandlers { defer_message_handler: DeferMessageHandler, dialog_message_handler: DialogMessageHandler, globals_message_handler: GlobalsMessageHandler, - input_preprocessor_message_handler: InputPreprocessorMessageHandler, + pub input_preprocessor_message_handler: InputPreprocessorMessageHandler, key_mapping_message_handler: KeyMappingMessageHandler, layout_message_handler: LayoutMessageHandler, pub portfolio_message_handler: PortfolioMessageHandler, - preferences_message_handler: PreferencesMessageHandler, + pub preferences_message_handler: PreferencesMessageHandler, tool_message_handler: ToolMessageHandler, } diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 5430726c7e..eeb7d46e35 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -119,10 +119,10 @@ pub struct DocumentMessageHandler { pub(crate) path: Option, /// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks #[serde(skip)] - breadcrumb_network_path: Vec, + pub breadcrumb_network_path: Vec, /// Path to network that is currently selected. Updated based on the most recently clicked panel. #[serde(skip)] - selection_network_path: Vec, + pub selection_network_path: Vec, /// Stack of document network snapshots for previous history states. #[serde(skip)] document_undo_history: VecDeque, diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index f66fdd4783..8ed797f360 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -18,7 +18,7 @@ pub struct MenuBarMessageHandler { pub has_selection_history: (bool, bool), pub message_logging_verbosity: MessageLoggingVerbosity, pub reset_node_definitions_on_open: bool, - pub native_node_graph_render: bool, + pub should_render_svelte_nodes: bool, pub make_path_editable_is_allowed: bool, pub data_panel_open: bool, pub layers_panel_open: bool, @@ -49,7 +49,7 @@ impl LayoutHolder for MenuBarMessageHandler { let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names; let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents; let reset_node_definitions_on_open = self.reset_node_definitions_on_open; - let native_node_graph_render = self.native_node_graph_render; + let should_render_svelte_nodes = self.should_render_svelte_nodes; let make_path_editable_is_allowed = self.make_path_editable_is_allowed; let menu_bar_entries = vec![ @@ -699,8 +699,8 @@ impl LayoutHolder for MenuBarMessageHandler { ..MenuBarEntry::default() }], vec![MenuBarEntry { - label: "Native Node Graph UI Render".into(), - icon: Some(if native_node_graph_render { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()), + label: "HTML Node Graph Render".into(), + icon: Some(if should_render_svelte_nodes { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()), action: MenuBarEntry::create_action(|_| NodeGraphMessage::ToggleNativeNodeGraphRender.into()), ..MenuBarEntry::default() }], diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 747d4f6d71..20f47d99aa 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -223,7 +223,7 @@ } -{#if !$nodeGraph.nativeNodeGraphRender} +{#if $nodeGraph.shouldRenderSvelteNodes}
+{:else} +
{@html $nodeGraph.nativeNodeGraphSVGString}
{/if}
@@ -772,6 +774,14 @@ } } + .native-node-graph-ui { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + .layers-and-nodes { position: absolute; top: 0; From 6c08b57775531d237465441483e4326b774c768d Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 12:37:39 -0700 Subject: [PATCH 5/8] fix should render nodes --- .../document/node_graph/node_graph_message_handler.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 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 41c78536f7..870a77b10e 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 @@ -95,7 +95,7 @@ pub struct NodeGraphMessageHandler { /// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend frontend_nodes: Vec, /// Disables rendering nodes in Svelte - native_node_graph_render: bool, + should_render_svelte_nodes: bool, } /// 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. @@ -1625,7 +1625,8 @@ impl<'a> MessageHandler> for NodeG responses.add(PropertiesPanelMessage::Refresh); responses.add(NodeGraphMessage::UpdateActionButtons); - if self.native_node_graph_render { + if !self.should_render_svelte_nodes { + // Generate and render node graph overlay network } else { let nodes_to_render = network_interface.collect_nodes(&self.node_graph_errors, preferences.graph_wire_style, breadcrumb_network_path); self.frontend_nodes = nodes_to_render.iter().map(|node| node.metadata.node_id).collect(); @@ -1789,9 +1790,9 @@ impl<'a> MessageHandler> for NodeG network_interface.toggle_preview(node_id, selection_network_path); } NodeGraphMessage::ToggleNativeNodeGraphRender => { - self.native_node_graph_render = !self.native_node_graph_render; + self.should_render_svelte_nodes = !self.should_render_svelte_nodes; responses.add(FrontendMessage::UpdateShouldRenderSvelteNodes { - should_render_svelte_nodes: self.native_node_graph_render, + should_render_svelte_nodes: self.should_render_svelte_nodes, }); responses.add(NodeGraphMessage::SendGraph); responses.add(MenuBarMessage::SendLayout); @@ -2627,7 +2628,7 @@ impl Default for NodeGraphMessageHandler { reordering_import: None, end_index: None, frontend_nodes: Vec::new(), - native_node_graph_render: false, + should_render_svelte_nodes: false, } } } From 88a9064dd6d3cb318c78fe8e9b3956eba31921b2 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 14:52:35 -0700 Subject: [PATCH 6/8] Dot background --- node-graph/gcore/src/node_graph_overlay.rs | 29 +----- .../src/node_graph_overlay/background.rs | 97 +++++++++++++++++++ .../gcore/src/node_graph_overlay/consts.rs | 1 + node-graph/gcore/src/vector/style.rs | 7 +- 4 files changed, 108 insertions(+), 26 deletions(-) create mode 100644 node-graph/gcore/src/node_graph_overlay/background.rs diff --git a/node-graph/gcore/src/node_graph_overlay.rs b/node-graph/gcore/src/node_graph_overlay.rs index 1e878a1491..4a437ec33d 100644 --- a/node-graph/gcore/src/node_graph_overlay.rs +++ b/node-graph/gcore/src/node_graph_overlay.rs @@ -1,8 +1,8 @@ -use graphene_core_shaders::{Ctx, color::Color}; -use kurbo::{BezPath, Point}; +use graphene_core_shaders::Ctx; use crate::{ node_graph_overlay::{ + background::generate_background, nodes_and_wires::{draw_layers, draw_nodes}, types::NodeGraphOverlayData, ui_context::{UIContext, UIRuntimeResponse}, @@ -12,6 +12,7 @@ use crate::{ vector::Vector, }; +pub mod background; pub mod consts; pub mod nodes_and_wires; pub mod types; @@ -38,29 +39,7 @@ pub fn transform_nodes(ui_context: UIContext, mut nodes: Table) -> Table #[node_macro::node(skip_impl)] pub fn dot_grid_background(ui_context: UIContext, opacity: f64) -> Table { - // From --color-2-mildblack: --color-2-mildblack-rgb: 34, 34, 34; - let gray = (34. / 255.) as f32; - let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, (opacity / 100.) as f32) else { - log::error!("Could not create color in dot grid background"); - return Table::new(); - }; - - let mut bez_path = BezPath::new(); - let p0 = Point::new(0., 0.); // bottom-left - let p1 = Point::new(ui_context.resolution.x as f64, 0.); // bottom-right - let p2 = Point::new(ui_context.resolution.x as f64, ui_context.resolution.y as f64); // top-right - let p3 = Point::new(0., ui_context.resolution.y as f64); // top-left - - bez_path.move_to(p0); - bez_path.line_to(p1); - bez_path.line_to(p2); - bez_path.line_to(p3); - bez_path.close_path(); - - let mut vector = Vector::from_bezpath(bez_path); - vector.style.fill = crate::vector::style::Fill::Solid(bg_color); - - Table::new_from_element(vector) + generate_background(ui_context, opacity) } #[node_macro::node(skip_impl)] diff --git a/node-graph/gcore/src/node_graph_overlay/background.rs b/node-graph/gcore/src/node_graph_overlay/background.rs new file mode 100644 index 0000000000..ccc7c49e5e --- /dev/null +++ b/node-graph/gcore/src/node_graph_overlay/background.rs @@ -0,0 +1,97 @@ +use graphene_core_shaders::color::Color; +use kurbo::{BezPath, Point}; + +use crate::{ + node_graph_overlay::{consts::*, ui_context::UIContext}, + table::{Table, TableRow}, + vector::{ + Vector, + style::{Fill, Stroke, StrokeCap}, + }, +}; + +pub fn generate_background(ui_context: UIContext, opacity: f64) -> Table { + // From --color-2-mildblack: --color-2-mildblack-rgb: 34, 34, 34; + let gray = (34. / 255.) as f32; + let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, (opacity / 100.) as f32) else { + log::error!("Could not create color in dot grid background"); + return Table::new(); + }; + + let mut bez_path = BezPath::new(); + let p0 = Point::new(0., 0.); // bottom-left + let p1 = Point::new(ui_context.resolution.x as f64, 0.); // bottom-right + let p2 = Point::new(ui_context.resolution.x as f64, ui_context.resolution.y as f64); // top-right + let p3 = Point::new(0., ui_context.resolution.y as f64); // top-left + + bez_path.move_to(p0); + bez_path.line_to(p1); + bez_path.line_to(p2); + bez_path.line_to(p3); + bez_path.close_path(); + + let mut vector = Vector::from_bezpath(bez_path); + vector.style.fill = Fill::Solid(bg_color); + + let mut bg_table = Table::new_from_element(vector); + + let mut grid_spacing = ui_context.transform.scale * GRID_SIZE; + while grid_spacing > 0. && grid_spacing < GRID_COLLAPSE_SPACING { + grid_spacing *= 2.; + } + let grid_dot_radius = 1. + (ui_context.transform.scale - 0.5 + 0.001).floor() / 2.; + let grid_offset_left = (ui_context.transform.x % grid_spacing + grid_spacing) % grid_spacing; + let grid_offset_top = (ui_context.transform.y % grid_spacing + grid_spacing) % grid_spacing; + + // make sure we cover full screen (+1 avoids missing last col/row) + let number_of_rows = (ui_context.resolution.y as f64 / grid_spacing).ceil() as u32 + 1; + // for col in 0..number_of_cols { + for row in 0..number_of_rows { + let circle_color = Color::from_rgba8_no_srgb(COLOR_7_MIDDLEGRAY).unwrap(); + let line_y = (row as f64 - 1.) * grid_spacing + grid_offset_top; + let mut line = BezPath::new(); + line.move_to(Point::new(grid_offset_left, line_y)); + line.line_to(Point::new(grid_offset_left + ui_context.resolution.x as f64, line_y)); + let mut line_vector = Vector::from_bezpath(line); + let dash_gap = grid_spacing - 0.00001; + let stroke_cap = StrokeCap::Round; + line_vector.style.stroke = Some( + Stroke::new(Some(circle_color), grid_dot_radius * 2.) + .with_dash_lengths(vec![0.00001, dash_gap]) + .with_stroke_cap(stroke_cap), + ); + bg_table.push(TableRow::new_from_element(line_vector)); + } + // } + bg_table +} + +// fn circle_bezpath(center: Point, radius: f64) -> BezPath { +// // "magic constant" for approximating a circle with 4 cubic Beziers +// let k = 0.5522847498307936; + +// let cx = center.x; +// let cy = center.y; +// let r = radius; +// let c = k * r; + +// let mut path = BezPath::new(); + +// // start at rightmost point +// path.move_to((cx + r, cy)); + +// // top-right quadrant +// path.curve_to((cx + r, cy + c), (cx + c, cy + r), (cx, cy + r)); + +// // top-left quadrant +// path.curve_to((cx - c, cy + r), (cx - r, cy + c), (cx - r, cy)); + +// // bottom-left quadrant +// path.curve_to((cx - r, cy - c), (cx - c, cy - r), (cx, cy - r)); + +// // bottom-right quadrant +// path.curve_to((cx + c, cy - r), (cx + r, cy - c), (cx + r, cy)); + +// path.close_path(); +// path +// } diff --git a/node-graph/gcore/src/node_graph_overlay/consts.rs b/node-graph/gcore/src/node_graph_overlay/consts.rs index 220d260ee8..1e3fedc7c0 100644 --- a/node-graph/gcore/src/node_graph_overlay/consts.rs +++ b/node-graph/gcore/src/node_graph_overlay/consts.rs @@ -1,4 +1,5 @@ pub const GRID_SIZE: f64 = 24.; +pub const GRID_COLLAPSE_SPACING: f64 = 10.; pub const BEZ_PATH_TOLERANCE: f64 = 0.1; // Keep in sync with colors in Editor.svelte diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gcore/src/vector/style.rs index 2850401270..4da0a11498 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gcore/src/vector/style.rs @@ -416,7 +416,7 @@ impl Stroke { self } - pub fn with_dash_lengths(mut self, dash_lengths: &str) -> Option { + pub fn with_dash_lengths_str(mut self, dash_lengths: &str) -> Option { dash_lengths .split(&[',', ' ']) .filter(|x| !x.is_empty()) @@ -429,6 +429,11 @@ impl Stroke { }) } + pub fn with_dash_lengths(mut self, dash_lengths: Vec) -> Self { + self.dash_lengths = dash_lengths; + self + } + pub fn with_dash_offset(mut self, dash_offset: f64) -> Self { self.dash_offset = dash_offset; self From 92a2978d229c517cb770a7ccc4e366def4d81765 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 3 Sep 2025 15:38:26 -0700 Subject: [PATCH 7/8] Improve layer --- .../src/node_graph_overlay/nodes_and_wires.rs | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs index 494e782f40..1c63191f6e 100644 --- a/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs +++ b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs @@ -1,5 +1,6 @@ +use glam::DVec2; use graphene_core_shaders::color::{AlphaMut, Color}; -use kurbo::{BezPath, RoundedRect, Shape}; +use kurbo::{BezPath, Rect, RoundedRect, Shape}; use crate::{ node_graph_overlay::{ @@ -65,30 +66,62 @@ pub fn draw_layers(nodes: &Vec) -> Table { let mut layer_table = Table::new(); for node_to_render in nodes { if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() { + // First render the text too get the layer width + let text_width: f64 = 0.; + + // The layer position is the top left of the thumbnail + let layer_position = DVec2::new(frontend_layer.position.x as f64 * GRID_SIZE + 0.5, frontend_layer.position.y as f64 * GRID_SIZE); + + // Width from the left of the thumbnail to the left border let chain_width = if frontend_layer.chain_width > 0 { frontend_layer.chain_width as f64 * GRID_SIZE + 0.5 * GRID_SIZE } else { 0. }; + // Width from the right of the thumbnail to the right border + let right_layer_width = text_width.max(4.5); + let thumbnail_width = 2. * GRID_SIZE; + let full_layer_width = chain_width + thumbnail_width + right_layer_width; - let x0 = frontend_layer.position.x as f64 * GRID_SIZE - chain_width + 0.5 * GRID_SIZE; - let y0 = frontend_layer.position.y as f64 * GRID_SIZE; + let x0 = layer_position.x - chain_width; + let y0 = layer_position.y; let h = 2. * GRID_SIZE; - let w = chain_width + 8. * GRID_SIZE - 0.5 * GRID_SIZE; - let rect = RoundedRect::new(x0, y0, x0 + w, y0 + h, 8.); - let bez_path = rect.to_path(BEZ_PATH_TOLERANCE); - let mut vector = Vector::from_bezpath(bez_path); - let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap(); - vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + // Background + let bg_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + full_layer_width, 8.); + let bez_path = bg_rect.to_path(BEZ_PATH_TOLERANCE); + let mut bg_vector = Vector::from_bezpath(bez_path); let mut background = if node_to_render.metadata.selected { Color::from_rgba8_no_srgb(COLOR_6_LOWERGRAY).unwrap() } else { Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap() }; background.set_alpha(0.33); - vector.style.fill = crate::vector::style::Fill::Solid(background); - layer_table.push(TableRow::new_from_element(vector)); + bg_vector.style.fill = crate::vector::style::Fill::Solid(background); + layer_table.push(TableRow::new_from_element(bg_vector)); + + // Border + let border_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + full_layer_width, 8.); + let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE); + let mut border_vector = Vector::from_bezpath(bez_path); + let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap(); + border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + layer_table.push(TableRow::new_from_element(border_vector)); + + // Border mask + let mut border_mask = BezPath::new(); + if frontend_layer.layer_has_left_border_gap && chain_width > 0.1 { + let left_input_mask = Rect::new(-8., 16., 8., 32.); + border_mask.extend(left_input_mask.to_path(BEZ_PATH_TOLERANCE)); + } + let thumbnail_mask = Rect::new(chain_width - 8., -2., chain_width + 72. + 8. * 2., 2. * GRID_SIZE + 2.); + border_mask.extend(thumbnail_mask.to_path(BEZ_PATH_TOLERANCE)); + let right_visibility_mask = Rect::new(full_layer_width - 12., 12., full_layer_width + 12., 36.); + border_mask.extend(right_visibility_mask.to_path(BEZ_PATH_TOLERANCE)); + let border_mask_vector = Vector::from_bezpath(border_mask); + let mut border_mask = TableRow::new_from_element(border_mask_vector); + border_mask.alpha_blending.clip = true; + layer_table.push(border_mask); } } layer_table From 33a8bb08d5c0e06bcc2f81923c2b920991cfafe4 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 4 Sep 2025 00:56:43 -0700 Subject: [PATCH 8/8] Masks --- editor/src/application.rs | 4 +- .../node_graph/generate_node_graph_overlay.rs | 17 +- node-graph/gcore/src/node_graph_overlay.rs | 21 ++- .../src/node_graph_overlay/nodes_and_wires.rs | 156 +++++++++++++----- .../interpreted-executor/src/node_registry.rs | 9 +- 5 files changed, 151 insertions(+), 56 deletions(-) diff --git a/editor/src/application.rs b/editor/src/application.rs index feafacd379..4b8c839955 100644 --- a/editor/src/application.rs +++ b/editor/src/application.rs @@ -51,7 +51,9 @@ impl Editor { in_selected_network: &active_document.selection_network_path == breadcrumb_network_path, previewed_node, }; - let node_graph_overlay_node = generate_node_graph_overlay(node_graph_render_data, active_document.graph_fade_artwork_percentage); + let opacity = active_document.graph_fade_artwork_percentage; + let font_cache = self.dispatcher.message_handlers.portfolio_message_handler.persistent_data.font_cache.clone(); + let node_graph_overlay_node = generate_node_graph_overlay(node_graph_render_data, opacity, font_cache); Some(NodeNetwork { exports: vec![NodeInput::node(NodeId(0), 0)], nodes: vec![(NodeId(0), node_graph_overlay_node)].into_iter().collect(), diff --git a/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs index d3d7a9f237..2d249243fd 100644 --- a/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs +++ b/editor/src/messages/portfolio/document/node_graph/generate_node_graph_overlay.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use graph_craft::{ concrete, document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork, value::TaggedValue}, @@ -5,11 +7,14 @@ use graph_craft::{ use graphene_std::{ node_graph_overlay::{types::NodeGraphOverlayData, ui_context::UIContext}, table::Table, + text::FontCache, uuid::NodeId, }; /// https://excalidraw.com/#json=LgKS6I4lQvGPmke06ZJyp,D9aON9vVZJAjNnZWfwy_SQ -pub fn generate_node_graph_overlay(node_graph_overlay_data: NodeGraphOverlayData, opacity: f64) -> DocumentNode { +pub fn generate_node_graph_overlay(node_graph_overlay_data: NodeGraphOverlayData, opacity: f64, font_cache: Arc) -> DocumentNode { + let font_cache_id = NodeId::new(); + let generate_nodes_id = NodeId::new(); let cache_nodes_id = NodeId::new(); let transform_nodes_id = NodeId::new(); @@ -34,12 +39,20 @@ pub fn generate_node_graph_overlay(node_graph_overlay_data: NodeGraphOverlayData implementation: DocumentNodeImplementation::Network(NodeNetwork { exports: vec![NodeInput::node(send_overlay_id, 0)], nodes: vec![ + ( + font_cache_id, + DocumentNode { + inputs: vec![NodeInput::value(TaggedValue::FontCache(font_cache), false)], + implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER), + ..Default::default() + }, + ), // Create the nodes ( generate_nodes_id, DocumentNode { call_argument: concrete!(UIContext), - inputs: vec![NodeInput::network(concrete!(UIContext), 1)], + inputs: vec![NodeInput::network(concrete!(UIContext), 1), NodeInput::node(font_cache_id, 0)], implementation: DocumentNodeImplementation::ProtoNode("graphene_core::node_graph_overlay::GenerateNodesNode".into()), ..Default::default() }, diff --git a/node-graph/gcore/src/node_graph_overlay.rs b/node-graph/gcore/src/node_graph_overlay.rs index 4a437ec33d..c57244a121 100644 --- a/node-graph/gcore/src/node_graph_overlay.rs +++ b/node-graph/gcore/src/node_graph_overlay.rs @@ -1,6 +1,9 @@ +use std::sync::Arc; + use graphene_core_shaders::Ctx; use crate::{ + Graphic, node_graph_overlay::{ background::generate_background, nodes_and_wires::{draw_layers, draw_nodes}, @@ -8,8 +11,8 @@ use crate::{ ui_context::{UIContext, UIRuntimeResponse}, }, table::Table, + text::FontCache, transform::ApplyTransform, - vector::Vector, }; pub mod background; @@ -19,31 +22,31 @@ pub mod types; pub mod ui_context; #[node_macro::node(skip_impl)] -pub fn generate_nodes(_: impl Ctx, node_graph_overlay_data: NodeGraphOverlayData) -> Table { +pub fn generate_nodes(_: impl Ctx, node_graph_overlay_data: NodeGraphOverlayData, font_cache: Arc) -> Table { let mut nodes_and_wires = Table::new(); - let layers = draw_layers(&node_graph_overlay_data.nodes_to_render); + let layers = draw_layers(&node_graph_overlay_data.nodes_to_render, font_cache.as_ref()); nodes_and_wires.extend(layers); - let nodes = draw_nodes(&node_graph_overlay_data.nodes_to_render); + let nodes = draw_nodes(&node_graph_overlay_data.nodes_to_render, font_cache.as_ref()); nodes_and_wires.extend(nodes); nodes_and_wires } #[node_macro::node(skip_impl)] -pub fn transform_nodes(ui_context: UIContext, mut nodes: Table) -> Table { +pub fn transform_nodes(ui_context: UIContext, mut nodes: Table) -> Table { let matrix = ui_context.transform.to_daffine2(); - nodes.apply_transform(&matrix); + nodes.left_apply_transform(&matrix); nodes } #[node_macro::node(skip_impl)] -pub fn dot_grid_background(ui_context: UIContext, opacity: f64) -> Table { - generate_background(ui_context, opacity) +pub fn dot_grid_background(ui_context: UIContext, opacity: f64) -> Table { + Table::new_from_element(Graphic::Vector(generate_background(ui_context, opacity))) } #[node_macro::node(skip_impl)] -pub fn node_graph_ui_extend(_: impl Ctx, new: Table, mut base: Table) -> Table { +pub fn node_graph_ui_extend(_: impl Ctx, new: Table, mut base: Table) -> Table { base.extend(new); base } diff --git a/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs index 1c63191f6e..8d5a62b839 100644 --- a/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs +++ b/node-graph/gcore/src/node_graph_overlay/nodes_and_wires.rs @@ -1,17 +1,21 @@ -use glam::DVec2; +use glam::{DAffine2, DVec2}; use graphene_core_shaders::color::{AlphaMut, Color}; use kurbo::{BezPath, Rect, RoundedRect, Shape}; use crate::{ + Graphic, + bounds::{BoundingBox, RenderBoundingBox}, node_graph_overlay::{ consts::*, types::{FrontendGraphDataType, FrontendNodeToRender}, }, table::{Table, TableRow}, + text::{self, FontCache, TextAlign, TypesettingConfig}, + transform::ApplyTransform, vector::{Vector, style::Fill}, }; -pub fn draw_nodes(nodes: &Vec) -> Table { +pub fn draw_nodes(nodes: &Vec, _font_cache: &FontCache) -> Table { let mut node_table = Table::new(); for node_to_render in nodes { if let Some(frontend_node) = node_to_render.node_or_layer.node.as_ref() { @@ -22,15 +26,12 @@ pub fn draw_nodes(nodes: &Vec) -> Table { let height = 1 + number_of_exposed_inputs; let h = height as f64 * GRID_SIZE; - let border_rect = RoundedRect::new(x, y, x + w, y + h, 2.); - let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE); - let mut border_vector = Vector::from_bezpath(bez_path); - let primary_output_color = frontend_node.outputs[0] - .as_ref() - .map(|primary_output| primary_output.data_type.data_color_dim()) - .unwrap_or(FrontendGraphDataType::General.data_color_dim()); - let border_color = Color::from_rgba8_no_srgb(primary_output_color).unwrap(); - border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + let node_rect = RoundedRect::new(x, y, x + w, y + h, 2.); + let node_bez_path = node_rect.to_path(BEZ_PATH_TOLERANCE); + + // Background table + let mut bg_table = Table::new(); + let mut bg_vector = Vector::from_bezpath(node_bez_path.clone()); let node_color = if node_to_render.metadata.selected { let mut selection_color = Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap(); selection_color.set_alpha(0.15); @@ -40,37 +41,65 @@ pub fn draw_nodes(nodes: &Vec) -> Table { bg_color.set_alpha(0.33); bg_color }; - border_vector.style.fill = crate::vector::style::Fill::Solid(node_color); - + bg_vector.style.fill = crate::vector::style::Fill::Solid(node_color.clone()); + bg_table.push(TableRow::new_from_element(bg_vector)); // Make primary input brighter if number_of_exposed_inputs == 0 { // Draw the first row with rounded bottom corners - node_table.push(TableRow::new_from_element(node_first_row(x, y, true))); + bg_table.push(TableRow::new_from_element(node_first_row(x, y, false))); } else { // Draw the first row without rounded bottom corners - node_table.push(TableRow::new_from_element(node_first_row(x, y, false))); - // for node_index in 0..(number_of_exposed_inputs - 1) { - // node_table.push(TableRow::new_from_element(node_secondary_row(x, y, node_index + 1, false))); - // } - // // Draw the last row with bottom corners - // node_table.push(TableRow::new_from_element(node_secondary_row(x, y, number_of_exposed_inputs, true))); + bg_table.push(TableRow::new_from_element(node_first_row(x, y, false))); }; + node_table.push(TableRow::new_from_element(Graphic::Vector(bg_table))); - node_table.push(TableRow::new_from_element(border_vector)); + // Border table + let mut border_table = Table::new(); + let mut border_vector = Vector::from_bezpath(node_bez_path); + let primary_output_color = frontend_node.outputs[0] + .as_ref() + .map(|primary_output| primary_output.data_type.data_color_dim()) + .unwrap_or(FrontendGraphDataType::General.data_color_dim()); + let border_color = Color::from_rgba8_no_srgb(primary_output_color).unwrap(); + border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); + border_table.push(TableRow::new_from_element(border_vector)); + node_table.push(TableRow::new_from_element(Graphic::Vector(border_table))); + + // Border mask table + let mut border_mask_table = Table::new(); + let mut border_masks = BezPath::new(); + if frontend_node.inputs[0].is_some() { + let index = 0; + border_masks.extend(Rect::new(-8., index as f64 * GRID_SIZE + 4., 8., index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE)); + } + for index in 1..=frontend_node.inputs.iter().skip(1).filter(|input| input.is_some()).count() { + border_masks.extend(Rect::new(-8., index as f64 * GRID_SIZE + 4., 8., index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE)); + } + if frontend_node.outputs[0].is_some() { + let index = 0; + border_masks.extend(Rect::new(-8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 4., 8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE)); + } + for index in 1..=frontend_node.outputs.iter().skip(1).filter(|output| output.is_some()).count() { + border_masks.extend(Rect::new(-8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 4., 8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE)); + } + let mut border_masks_vector = Vector::from_bezpath(border_masks); + border_masks_vector.style.fill = Fill::Solid(node_color); + let mut border_masks_row = TableRow::new_from_element(border_masks_vector); + border_masks_row.alpha_blending.clip = true; + border_masks_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x, y))); + border_mask_table.push(border_masks_row); + node_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table))); } } node_table } -pub fn draw_layers(nodes: &Vec) -> Table { +pub fn draw_layers(nodes: &Vec, font_cache: &FontCache) -> Table { let mut layer_table = Table::new(); for node_to_render in nodes { if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() { - // First render the text too get the layer width - let text_width: f64 = 0.; - // The layer position is the top left of the thumbnail - let layer_position = DVec2::new(frontend_layer.position.x as f64 * GRID_SIZE + 0.5, frontend_layer.position.y as f64 * GRID_SIZE); + let layer_position = DVec2::new(frontend_layer.position.x as f64 * GRID_SIZE + 12., frontend_layer.position.y as f64 * GRID_SIZE); // Width from the left of the thumbnail to the left border let chain_width = if frontend_layer.chain_width > 0 { @@ -78,9 +107,39 @@ pub fn draw_layers(nodes: &Vec) -> Table { } else { 0. }; - // Width from the right of the thumbnail to the right border - let right_layer_width = text_width.max(4.5); - let thumbnail_width = 2. * GRID_SIZE; + + // First render the text to get the layer width + // Create typesetting configuration + let typesetting = TypesettingConfig { + font_size: 14., + line_height_ratio: 1.2, + character_spacing: 0.0, + max_width: None, + max_height: None, + tilt: 0.0, + align: TextAlign::Left, + }; + + let font_blob = Some(text::load_font(font_cache.source_sans_pro())); + let mut text_table = crate::text::to_path(&node_to_render.metadata.display_name, font_blob, typesetting, false); + + let text_width = if let RenderBoundingBox::Rectangle(bbox) = text_table.bounding_box(DAffine2::default(), true) { + bbox[1].x - bbox[0].x + } else { + 0. + }; + + // Text starts at thumbnail + left padding + let text_start = 12. + 8.; + let right_text_edge = text_start + text_width; + let rounded_text_edge = (right_text_edge as f64 / 24.).ceil() * 24.; + + let rounded_layer_width_pixels = rounded_text_edge + 24.; + // Subtract the left thumbnail + let layer_right_edge_width = rounded_layer_width_pixels - 12.; + + let right_layer_width = layer_right_edge_width.max(4.5 * GRID_SIZE); + let thumbnail_width = 3. * GRID_SIZE; let full_layer_width = chain_width + thumbnail_width + right_layer_width; let x0 = layer_position.x - chain_width; @@ -88,7 +147,8 @@ pub fn draw_layers(nodes: &Vec) -> Table { let h = 2. * GRID_SIZE; // Background - let bg_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + full_layer_width, 8.); + let mut background_table = Table::new(); + let bg_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + h, 8.); let bez_path = bg_rect.to_path(BEZ_PATH_TOLERANCE); let mut bg_vector = Vector::from_bezpath(bez_path); let mut background = if node_to_render.metadata.selected { @@ -97,18 +157,22 @@ pub fn draw_layers(nodes: &Vec) -> Table { Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap() }; background.set_alpha(0.33); - bg_vector.style.fill = crate::vector::style::Fill::Solid(background); - layer_table.push(TableRow::new_from_element(bg_vector)); + bg_vector.style.fill = Fill::Solid(background.clone()); + background_table.push(TableRow::new_from_element(bg_vector)); + layer_table.push(TableRow::new_from_element(Graphic::Vector(background_table))); // Border - let border_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + full_layer_width, 8.); + let mut border_table = Table::new(); + let border_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + h, 8.); let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE); let mut border_vector = Vector::from_bezpath(bez_path); let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap(); border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.)); - layer_table.push(TableRow::new_from_element(border_vector)); + border_table.push(TableRow::new_from_element(border_vector)); + layer_table.push(TableRow::new_from_element(Graphic::Vector(border_table))); // Border mask + let mut border_mask_table = Table::new(); let mut border_mask = BezPath::new(); if frontend_layer.layer_has_left_border_gap && chain_width > 0.1 { let left_input_mask = Rect::new(-8., 16., 8., 32.); @@ -118,12 +182,24 @@ pub fn draw_layers(nodes: &Vec) -> Table { border_mask.extend(thumbnail_mask.to_path(BEZ_PATH_TOLERANCE)); let right_visibility_mask = Rect::new(full_layer_width - 12., 12., full_layer_width + 12., 36.); border_mask.extend(right_visibility_mask.to_path(BEZ_PATH_TOLERANCE)); - let border_mask_vector = Vector::from_bezpath(border_mask); - let mut border_mask = TableRow::new_from_element(border_mask_vector); - border_mask.alpha_blending.clip = true; - layer_table.push(border_mask); + let mut border_mask_vector = Vector::from_bezpath(border_mask); + border_mask_vector.style.fill = Fill::Solid(background); + let mut border_mask_row = TableRow::new_from_element(border_mask_vector); + border_mask_row.alpha_blending.clip = true; + border_mask_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x0, y0))); + border_mask_table.push(border_mask_row); + layer_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table))); + + // The top layer contains the ports,thumbnail,text, etc + for text_row in text_table.iter_mut() { + text_row.element.style.fill = Fill::Solid(Color::WHITE); + *text_row.transform = DAffine2::from_translation(layer_position + DVec2::new(thumbnail_width + text_start, 16.)); + } + let top_layer = text_table; + layer_table.push(TableRow::new_from_element(Graphic::Vector(top_layer))); } } + layer_table } @@ -133,6 +209,8 @@ fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector { let r = 2.; let bez_path = if rounded_bottom { + RoundedRect::new(x0, y0, x1, y1, r).to_path(BEZ_PATH_TOLERANCE) + } else { let mut path = BezPath::new(); // Start at bottom-left path.move_to((x0, y1)); @@ -157,8 +235,6 @@ fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector { path.close_path(); path - } else { - RoundedRect::new(x0, y0, x1, y1, r).to_path(BEZ_PATH_TOLERANCE) }; let mut vector = Vector::from_bezpath(bez_path); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 50b40d0d0b..79fa41d27c 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -24,6 +24,7 @@ use graphene_std::gradient::GradientStops; use graphene_std::node_graph_overlay::types::NodeGraphOverlayData; use graphene_std::node_graph_overlay::ui_context::UIContext; use graphene_std::table::Table; +use graphene_std::text::FontCache; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; #[cfg(feature = "gpu")] @@ -185,11 +186,11 @@ fn node_registry() -> HashMap, input: UIContext, fn_params: [UIContext => NodeGraphOverlayData]), - async_node!(graphene_core::node_graph_overlay::TransformNodesNode<_>, input: UIContext, fn_params: [UIContext =>Table]), + async_node!(graphene_core::node_graph_overlay::GenerateNodesNode<_, _>, input: UIContext, fn_params: [UIContext => NodeGraphOverlayData, UIContext => Arc]), + async_node!(graphene_core::node_graph_overlay::TransformNodesNode<_>, input: UIContext, fn_params: [UIContext =>Table]), async_node!(graphene_core::node_graph_overlay::DotGridBackgroundNode<_>, input: UIContext, fn_params: [UIContext =>f64]), - async_node!(graphene_core::node_graph_overlay::NodeGraphUiExtendNode<_, _>, input: UIContext, fn_params: [UIContext =>Table, UIContext =>Table]), - async_node!(graphene_std::wasm_application_io::RenderNodeGraphUiNode<_>, input: UIContext, fn_params: [UIContext =>Table]), + async_node!(graphene_core::node_graph_overlay::NodeGraphUiExtendNode<_, _>, input: UIContext, fn_params: [UIContext =>Table, UIContext =>Table]), + async_node!(graphene_std::wasm_application_io::RenderNodeGraphUiNode<_>, input: UIContext, fn_params: [UIContext =>Table]), async_node!(graphene_core::node_graph_overlay::SendRenderNode<_>, input: UIContext, fn_params: [UIContext => String]), ]; // =============