From 24ceb179b99c1119e8302d50a92ea65c0e02046a Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 4 Sep 2025 00:56:43 -0700 Subject: [PATCH] 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 b3695280fc..de4bf73c4c 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 4bc7b8babe..3ade5481e9 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::transform::Footprint; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; @@ -253,11 +254,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]), ]; // =============